React教程 - 4. 组件和属性

本文译自React官方文档
全文翻译及相关代码,请参看我的Github

组件使你能够将UI划分为独立的,可复用的个体。
从概念上来说,组件类似于JavaScript中的函数。
他们接受任意输入(这些输入被称为属性-props)并返回React元素,描述其在屏幕上应该如何显示。

1.函数式组件和类组件

定义组件最简单的方式是写一个JavaScript函数:

1
2
3
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

这个函数是一个有效的React组件-其接受一个单一的,拥有数据的props对象作为参数并返回一个React元素。
由于他们在字面上看来是函数,我们将这类组件成为”函数式组件”(Functional)
您也可以使用ES6的class定义一个组件:

1
2
3
4
5
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

以上两种方式对React而言是一样的。
我们将在下一部分讨论class拥有的一些额外的特性。
在此之前,由于函数式组件更简洁,我们将使用函数式组件。

2.渲染一个组件

之前,我们只遇到过代表DOM标签的React元素:

1
const element = <div />;

然而,元素也能够代表用户定义的组件:

1
const element = <Welcome name="Sara" />;

当React发现某个代表用户定义组件的元素时,它将把JSX的属性作为一个单独的对象传递给这个组件。我们将这个对象称为”属性(props)”。
比如,以下代码将在页面上渲染”Hello, Sara”:

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

const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);

在CodePen中尝试
在这个例子中:

  1. 我们将<Welcome name="Sara" />作为参数调用了ReactDOM.render()
  2. React将{name: 'Sara'}作为属性调用了Welcome组件
  3. Welcome组件返回一个<h1>Hello, Sara</h1>元素
  4. React DOM高效的更新DOM-使其与<h1>Hello, Sara</h1>匹配

注意
为组件命名时首字母应大写。
比如说,<div />代表一个DOM标签,但<Welcome />则代表一个组件。

3.构建组件

组件可以在输出中引用其他组件,这使我们能够对任何级别的粒度划分执行组件抽象。一个按钮、一个表单,一个对话框-在React应用中,这些通常都被作为一个组件。
例如,我们可以创建一个App组件用于多次渲染Welcome

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);

}

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

在CodePen中尝试
通常来说,新的React应用在最顶层有一个App组件。但是,如果您是将React引入至某个现有的应用中,你从可以使用如Button这样的小组件开始,自底向上逐步替换。

注意
组件比如返回一个单独的根元素。这也是我们为<Welcome />元素外面包裹上一层<div>的原因

4.分解组件

不要担心将组件分割成更细粒度的组件。
例如,考虑如下组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>

<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);

}

在CodePen中尝试
该组件接受”作者(一个对象)”, “文字(一个字符串)”, 和一个”日期”作为属性,并在一个社交媒体网站上描述了评论。
由于过多的嵌套,这个组件难以被替换和复用。我们可以考虑对其进行分解。
首先,我们可以提取出Avatar:

1
2
3
4
5
6
7
8
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>

);

}

我们将user(一个更通用的名字)而非author作为其属性-Avatar不需要知道其是否是用于Comment的
我们建议从组件的角度而非其具体的应用场景对组件的属性进行命名。
现在可以对Comment组件做一下简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<Avatar user={props.author} />
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);

}

接下来,我们接着提取出一个UserInfo组件-该组件用于将Avatar组件放在用户旁边:

1
2
3
4
5
6
7
8
9
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);

现在Comment组件可以进一步简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);

}

在CodePen中尝试
提取组件的工作在开始时看起来意义不大,但在大型应用中,一个能复用的组件库能够大大的减轻开发成本。
一个好的经验是-如果你的某个组件要用到几次(如按钮,Avatar等),或其拥有一定的复杂度(App, FeedStory, Commen等),则有必要考虑是否需要将其作为可复用组件进行提取。

5.组件的属性是只读的

无论将组件声明为一个函数或一个类,其属性均是不可变的。考虑以下函数sum

1
2
3
function sum(a, b) {
return a + b;
}

这种函数被称为纯函数-他们不企图改变输入的值;对于相同的输入,总是返回相同的结果。
作为对比,下面这个函数不是纯函数(其改变了他的输入):

1
2
3
function withdraw(account, amount) {
account.total -= amount;
}

React非常灵活,但它有一个严格的规定:
所有的React组件必须要像纯函数一样去接受他们的props
当然,应用的UI是动态的,会随着时间改变。在下一部分,我们将介绍一个新的概念-状态(state)。不同于属性,状态允许组件对其进行修改。

本文首发于http://www.miaoyunze.com/,转载请注明出处