JSX

JSX是 JavaScrip 的一种扩展语法,可以生成 React “元素”。

JSX中可以嵌入js表达式。

JSX是一个表达式,编译之后,就变成了常规的 JavaScript 对象(想想js对象可以干什么,就可以用JSX来干什么,比如:在 if 语句或者是 for 循环中使用 JSX,用它给变量赋值,当做参数接收,或者作为函数的返回值)。

比起HTML,JSX更接近于JavaScript,所以React DOM使用驼峰(camelCase)属性命名约定, 而不是HTML属性名称。例如,class 在JSX中变为className,tabindex 变为 tabIndex。

Babel 将JSX编译成 React.createElement() 调用
React.createElement()会创建下面这样一个对象:

1
2
3
4
5
6
7
8
// 注意: 这是简化的结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};

了解了上述步骤,仔细这个对象,就应该意识到:

  • 空标签应该闭合
  • 子元素散开的,应该在外层加个父元素包裹起来
1
2
//应该用"/"闭合
const element = <img src={user.avatarUrl} />;
1
2
3
4
5
//这是错的,没法创建一个完整的对象
const element = (
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
);
1
2
3
4
5
6
7
8
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
//添加一个div包裹
//多行时,用圆括号包裹起来,可减少意外添加分号的错误

元素渲染

不同于浏览器的 DOM 元素, React 元素是普通的对象,非常容易创建。React DOM 会负责更新 DOM ,以匹配React元素

React 元素是不可突变(immutable)的. 一旦你创建了一个元素, 就不能再修改其子元素或任何属性。一个元素就像电影里的一帧: 它表示在某一特定时间点的 UI 。
更新 UI 的唯一方法是创建一个新的元素, 并将其传入 ReactDOM.render() 方法。
实际上,大多数 React 应用只会调用 ReactDOM.render() 一次,好的解决方法是将代码封装到有状态的组件中。

React DOM 会将元素及其子元素与之前版本逐一对比, 并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。


组件(Components) 和 属性(Props)

组件就像JavaScript的函数,可以接收任意输入(称为”props”), 并返回 React 元素,用以描述屏幕显示内容。

渲染一个组件

元素也可以代表用户定义的组件
当 React 遇到一个代表用户定义组件的元素时,它将 JSX 属性以一个单独对象的形式传递给相应的组件。 我们将其称为 “props”对象。

1
2
3
4
5
6
7
8
9
10
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
//在页面上渲染 “Hello, Sara”

重述一下上面这个例子:

  1. 我们调用了 ReactDOM.render() 方法并向其中传入了 <Welcome name="Sara" /> 元素。
  2. React 调用 Welcome 组件,并向其中传入了 {name: 'Sara'} 作为 props 对象。
  3. Welcome 组件返回 <h1>Hello, Sara</h1>
  4. React DOM 迅速更新 DOM,使其显示为 <h1>Hello, Sara</h1>

注意:组件名称总是以大写字母开始

Props 是只读的


状态(State) 和 生命周期

1
2
3
4
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
//函数式组件
1
2
3
4
5
6
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
//类组件

把函数式组件转换为类组件:

  1. 创建一个继承自 React.Component 类的 ES6 class 同名类。
  2. 添加一个名为 render() 的空方法。
  3. 把原函数中的所有内容移至 render() 中。
  4. 在 render() 方法中使用 this.props 替代 props。
  5. 删除保留的空函数声明。

state 和 props 类似,但是它是私有的,并且由组件本身完全控制。

用类定义的组件有一些额外的特性, 这个”类专有的特性”, 指的就是局部状态。

1
2
3
4
5
constructor(props) {
super(props);
this.state = {date: new Date()};
}
//类组件应始终使用 props 调用基础构造函数

在类中添加生命周期方法

先了解下React 生命周期的三种状态: 1. 初始化 2.更新 3.销毁(参照https://www.cnblogs.com/qiaojie/p/6135180.html

初始化:

  1. getDefaultProps(),设置默认的props,也可以用dufaultProps设置组件的默认属性
  2. 初始化状态,在constructor中定义this.state,此时可以访问this.props
  3. componentWillMount(),组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state
  4. render(),React最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行,此时就不能更改state了
  5. componentDidMount(),组件渲染之后调用,只调用一次

更新:

  1. componentWillReceiveProps(nextProps),组件初始化时不调用,组件接受新的props时调用
  2. shouldComponentUpdate(nextProps, nextState),react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候
  3. componentWillUpdata(nextProps, nextState),组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state
  4. render(),组件渲染
  5. componentDidUpdate(),组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点

卸载:

  1. componentWillUnmount(),组件将要卸载时调用,一些事件监听和定时器需要在此时清除

在一个具有许多组件的应用程序中,在组件被销毁时释放所占用的资源是非常重要的。

this.props 由 React 本身设定, 而 this.state 具有特殊的含义,但如果需要存储一些不用于视觉输出的内容,则可以手动向类中添加额外的字段。

如果在 render() 方法中没有被引用, 它不应该出现在 state 中。

复用和封装

最原始粗暴,通过调 ReactDOM.render() 方法来更新渲染的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}

setInterval(tick, 1000);

通过封装,变得更复用:

  1. 把函数式组件转化为类组件
  2. 在类组件中添加本地状态(state)
  3. 在类中添加生命周期方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
date: new Date()
});
}

render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

ReactDOM.render(
<Clock />,
document.getElementById('root')
);

回顾一下该过程,以及调用方法的顺序:

  1. <Clock /> 被传入 ReactDOM.render() 时, React 会调用 Clock 组件的构造函数。 因为 Clock 要显示的是当前时间,所以它将使用包含当前时间的对象来初始化 this.state 。我们稍后会更新此状态。
  2. 然后 React 调用了 Clock 组件的 render() 方法。 React 从该方法返回内容中得到要显示在屏幕上的内容。然后,React 然后更新 DOM 以匹配 Clock 的渲染输出。
  3. 当 Clock 输出被插入到 DOM 中时,React 调用 componentDidMount() 生命周期钩子。在该方法中,Clock 组件请求浏览器设置一个定时器来一次调用 tick()。
  4. 浏览器会每隔一秒调用一次 tick() 方法。在该方法中, Clock 组件通过 setState() 方法并传递一个包含当前时间的对象来安排一个 UI 的更新。通过 setState(), React 得知了组件 state(状态)的变化, 随即再次调用 render() 方法,获取了当前应该显示的内容。这次,render() 方法中的 this.state.date 的值已经发生了改变,从而,其输出的内容也随之改变。React 于是据此对 DOM 进行更新。
  5. 如果通过其他操作将 Clock 组件从 DOM 中移除了, React 会调用 componentWillUnmount() 生命周期钩子, 所以计时器也会被停止。

正确地使用 State(状态)(这部分仔细看看文档上的例子说明http://www.css88.com/react/docs/state-and-lifecycle.html):

  • 不要直接修改 state(状态),用 setState() 代替
  • state(状态) 更新可能是异步的,React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新
  • state(状态) 更新会被合并,当你调用 setState(), React 将合并你提供的对象到当前的状态中

setState() 的另一种使用形式:它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数

数据向下流动

一个组件(无论作为父组件还是子组件)无法获取另一个组件的信息(是否有状态,是函数组件还是类组件)(因此, state 经常被称为 本地状态 或 封装状态),但可以选择将 state(状态) 向下传递,作为其子组件的 props(属性)

单向数据流, 任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件


处理事件

通过 React 元素处理事件跟在 DOM 元素上处理事件的区别:

  • React 事件使用驼峰命名,而不是全部小写
  • 通过 JSX , 你传递一个函数作为事件处理程序,而不是一个字符串
  • 在 React 中你不能通过返回 false 来阻止默认行为,必须明确调用 preventDefault 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ActionLink() {
function handleClick(e) {
e.preventDefault();//明确调用 preventDefault 方法
console.log('The link was clicked.');
}

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
/*
这里的 e 是一个合成的事件, React 根据 W3C 规范 定义了这个合成事件,
所以你不需要担心跨浏览器的兼容性问题。
*/

当使用 React 时,你一般不需要调用 addEventListener 在 DOM 元素被创建后添加事件监听器。相反,只要当元素被初始渲染的时候提供一个监听器就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};

// 这个绑定是必要的,使`this`在回调中起作用
this.handleClick = this.handleClick.bind(this);
/*
在JSX回调中你必须注意 this 的指向。在 JavaScript 中,类方法默认没有绑定的。
如果你忘记绑定 this.handleClick 并将其传递给onClick,
那么在直接调用该函数时,this 会是 undefined 。

这不是 React 特有的行为;这是 JavaScript 中的函数如何工作的一部分。
一般情况下,如果你引用一个后面没跟 () 的方法,例如 onClick={this.handleClick} ,
那你就应该 绑定(bind) 该方法。
*/
}

handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
//setState() 的另一种使用形式:它接受一个函数而不是一个对象
//此处省略了第二个参数
}

render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}

ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
1
2
3
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
<!--将参数传递给事件处理程序,两种方式,参数 e 作为 React 事件对象将会被作为第二个参数进行传递-->

条件渲染

1
2
3
4
5
6
7
//元素变量,可以用变量来存储元素
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
1
2
3
4
5
6
//使用逻辑 && 操作符的内联 if 用法
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
1
2
3
4
5
6
//使用条件操作符的内联 If-Else
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
1
2
3
4
5
6
7
8
9
10
11
12
//防止组件渲染,返回 null 而不是其渲染输出
function WarningBanner(props) {
if (!props.warn) {
return null;
}

return (
<div className="warning">
Warning!
</div>
);
}

列表(Lists) 和 键(Keys)

1
2
3
4
5
6
7
8
9
10
//多组件渲染
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);

ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//基本列表组件
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
/*
键(Keys) 帮助 React 标识哪个项被修改、添加或者移除了
数组中的每一个元素都应该有一个唯一不变的键(Keys)来标识
*/
);
return (
<ul>{listItems}</ul>
);
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
  • 在元素中调用 map() 的地方制定 keys
  • keys 在同辈元素中必须是唯一的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//在 JSX 中嵌入 map()
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />

)}
</ul>
);
}
//JSX允许在大括号中嵌入任何表达式,因此可以 内联 map() 结果

表单(Forms)

在 HTML 中,表单元素如 <input><textarea><select> 表单元素通常保持自己的状态,并根据用户输入进行更新。而在 React 中,可变状态一般保存在组件的 state(状态) 属性中,并且只能通过 setState() 更新。

我们可以通过“使 React 的 state 成为 单一数据源 ”原则 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。这种形式,其值由 React 控制的输入表单元素称为“受控组件”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
this.setState({value: event.target.value});
}
//html自身的event.target.value与React的state结合

handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

对于受控组件来说,每一次 state(状态) 变化都会伴有相关联的处理函数。这使得可以直接修改或验证用户的输入。比如,如果我们希望强制 name 的输入都是大写字母,可以这样来写 handleChange 方法:

1
2
3
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
textare 标签
用 value 属性替代<textarea>的赋值,表单中<textarea>的书写方式接近于单行文本输入框
下面仅展示了部分代码
*/
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};

handleChange(event) {
this.setState({value: event.target.value});
}

return <textarea value={this.state.value} onChange={this.handleChange} />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
select 标签
在根 select 标签中使用了一个 value 属性,来选中选项
下面仅展示了部分代码
*/
this.state = {value: 'coconut'};

handleChange(event) {
this.setState({value: event.target.value});
}

return (
...
<select value={this.state.value} onChange={this.handleChange}>
...
)

//可以将一个数组传递给 value 属性,允许你在 select 标签中选择多个选项:
<select multiple={true} value={['B', 'C']}>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
file input 标签
在 React 中,一个 <input type =“file”/> 和一个普通的 <input /> 类似,
但有一个重要的区别:它是只读的(read-only)。(您不能以编程方式设置值。)
相反,你应该使用 File API 与文件进行交互。
以下示例显示了如何使用一个 ref 来访问提交处理程序中的文件:
*/
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(
this
);
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${
this.fileInput.files[0].name
}`
);
}

render() {
return (
<form
onSubmit={this.handleSubmit}>
<label>
Upload file:
<input
type="file"
ref={input => {
this.fileInput = input;
}}
/>
</label>
<br />
<button type="submit">
Submit
</button>
</form>
);
}
}

ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
处理多个输入元素
当您需要处理多个受控的 input 元素时,您可以为每个元素添加一个 name 属性,
并且让处理函数根据 event.target.name 的值来选择要做什么。
*/
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};

this.handleInputChange = this.handleInputChange.bind(this);
}

handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;

this.setState({
[name]: value
});
}

render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}

状态提升(Lifting State Up)

通常情况下,同一个数据的变化需要几个不同的组件来反映。我们建议提升共享的状态到它们最近的祖先组件中。

在 React 中,共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。
共享状态的原理:
props(属性) 是只读的,而state可以通过调用this.setState()改变,因此,用props代替state,该props的值由父组件提供。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}

handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}

handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}

render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />

<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />

<BoilingVerdict
celsius={parseFloat(celsius)} />

</div>
);
}
}

现在,无论你编辑哪个输入框,Calculator 中的 this.state.temperature 和 this.state.scale 都会更新。其中一个输入框获取值,所以任何用户输入都被保留,并且另一个输入总是基于它重新计算值。

让我们回顾一下编辑输入时会发生什么:

  • React 调用在 DOM <input> 上的 onChange 指定的函数。在我们的例子中,这是 TemperatureInput 组件中的 handleChange 方法。
  • TemperatureInput 组件中的 handleChange 方法使用 新的期望值 调用 this.props.onTemperatureChange()TemperatureInput 组件中的 props(属性) ,包括 onTemperatureChange,由其父组件 Calculator 提供。
  • 当它预先呈现时, Calculator 指定了摄氏 TemperatureInputonTemperatureChangeCalculatorhandleCelsiusChange 方法,并且华氏 TemperatureInputonTemperatureChangeCalculatorhandleFahrenheitChange 方法。因此,会根据我们编辑的输入框,分别调用这两个 Calculator 方法。
  • 在这些方法中, Calculator 组件要求 React 通过使用 新的输入值 和 刚刚编辑的输入框的当前度量衡 来调用 this.setState() 来重新渲染自身。
  • React 调用 Calculator 组件的 render 方法来了解 UI 外观应该是什么样子。基于当前温度和激活的度量衡来重新计算两个输入框的值。这里进行温度转换。
  • React 使用 Calculator 指定的新 props(属性) 调用各个 TemperatureInput 组件的 render 方法。 它了解 UI 外观应该是什么样子。
  • React DOM 更新 DOM 以匹配期望的输入值。我们刚刚编辑的输入框接收当前值,另一个输入框更新为转换后的温度。

经验总结

在一个 React 应用中,对于任何可变的数据都应该循序“单一数据源”原则。通常情况下,state 首先被添加到需要它进行渲染的组件。然后,如果其它的组件也需要它,你可以提升状态到它们最近的祖先组件。你应该依赖 从上到下的数据流向 ,而不是试图在不同的组件中同步状态。

提升状态相对于双向绑定方法需要写更多的“模板”代码,但是有一个好处,它可以更方便的找到和隔离 bugs。由于任何 state(状态) 都 “存活” 在若干的组件中,而且可以分别对其独立修改,所以发生错误的可能大大减少。另外,你可以实现任何定制的逻辑来拒绝或者转换用户输入。

如果某个东西可以从 props(属性) 或者 state(状态) 得到,那么它可能不应该在 state(状态) 中。例如,我们只保存最后编辑的 temperature 和它的 scale,而不是保存 celsiusValue 和 fahrenheitValue 。另一个输入框的值总是在 render() 方法中计算得来的。这使我们对其进行清除和四舍五入到其他字段同时不会丢失用户输入的精度。

React 开发者工具: https://github.com/facebook/react-devtools


组合 VS 继承(Composition vs Inheritance)

一些组件在设计前无法获知自己要使用什么子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}

function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
//如 <Contacts /> 和 <Chat /> 等 React 元素本质上也是对象,
//所以可以将其像其他数据一样作为 props(属性) 传递使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}

function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />

);
}
//一个偏“特殊”的组件渲染出一个偏“通用”的组件

总结:
使用 props(属性) 和 组合已经足够灵活来明确、安全的定制一个组件的外观和行为。切记,组件可以接受任意的 props(属性) ,包括原始值、React 元素,或者函数。
如果要在组件之间重用非 U I功能,我们建议将其提取到单独的 JavaScript 模块中。组件可以导入它并使用该函数,对象或类,而不扩展它。