Components和Props
Components使得UI被划分为一个个独立、可复用的零件,并单独考虑每个零件。
在概念上,组件就像是JavaScript函数。他们接受任意的输入(称为"props"),并返回React elements来描述屏幕上应该出现的内容。
函数式Components和class Components
定义一个Component最简单的方法就是写个JavaScript函数:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
这个函数是一个有效的React component,因为他接受一个名为'props'对象的参数,并且返回一个React element。我们称这种组件为'函数式'的,因为他们就是普通的JavaScript函数。
你也可以用一个ES6 class来定义component:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
从React的角度来看,上面这两个components是相同的。
我们将在下一节讨论,Classes方式中的一些额外特性。在那之前,方便起见,我们将使用函数式的components。
渲染Component
之前,我们至于见过表现DOM标签的React elements:
const element = <div />;
不仅如此,elements也可以表示用户自定义的components:
const element = <Welcome name="Sara" />
当React发现一个表示用户自定义组件的element,它会把JSX属性作为一个单独的对象传给这个component。我们将这个对象成为'props'。
举个例子,下面的代码在页面上渲染出"Hello, Sara":
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
在CodePen上试试看
让我们重新过一遍这个例子中发生了什么:
- 我们调用
ReactDOM.render()
函数去渲染element:<Welcome name="Sara" />
。 - React调用
Wecome
组件,将{name: 'Sara'}
作为props。 - 我们的
Welcome
组件返回一个element:<h1>Hello, Sara</h1>
作为结果。 - React DOM高效地更新DOM来匹配
<h1>Hello, Sara</h1>
。
警告:
component名字的首字母始终都是大写。
比如,<div />
表示一个DOM标签,但是<Welcome />
表示一个component,而且需要将Welcome
加入当前范围内。
组合Components
Components可以在他们的输出中引用其他的components。这样我们就可以使用相同的组件来叠加完成任意的细节。按钮,表单,对话框和屏幕,在React应用中,这些通常都表示为组件。
比如:我们可以创建一个App
组件用来渲染多个Welcome
:
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集成到现有的应用中,你可以自下而上,从一个小的component开始,比如一个按钮,逐步的整合到层次结构的顶层。
警告:
Components必须返回一个单独的根element。这就是为什么我们添加一个<div>
来包括所有的<Welcome />
elements。
提取Components
大胆的将components分割成更小的components。
比如,考虑这个Comment
component:
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上试一试
它接收author
(一个object),text
(一个字符串)和date
(一个date)作为props,描述了社交媒体网站上一则评论。
由于全是嵌套,这个component很难改变,而且也很难去重用他的各个部分。让我们从中提取几个组件。
首先,我们来提取Avatar
:
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
Avatar
不需要知道Comment
里面究竟在渲染什么东西。因此我们传给prop一个更为通用的名字:user
,而不是author
。
我们建议从组件自身的观点来命名props,而不是依据组件所在的环境。
现在我们就稍微简化了下Comment
:
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
和用户的名字:
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
这使得我们可以进一步简化Comment
:
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上试一试
提取components也许乍看之下像是无用功,但在大型的app中,能有一系列可重用的组件,这么做绝对超值。一个很好的经验法则:如果你的UI中某部分被重复使用多次(按钮
,面板
,头像
),或者自身相当复杂(App
,FeedStory
,评论
), 这都是成为可重用组件的最佳人选。
Props是只读的
不论你将component声明为函数还是类,都绝不能去修改它的props。看下这个sum
函数:
function sum(a, b) {
return a + b;
}
这样的函数我们称其为"pure",因为他们不回去改变输入,且对于输入一样返回的结果始终都是相同的。
相比之下,这个函数不是纯函数,因为它改变了自己的输入:
function withdraw(account, amount) {
account.total -= amount;
}
React虽然相当灵活,但它有一个严格的规则:
所有的React组件必须像个纯函数一样,不得修改他们的props。
当然,应用的UI是动态的,随着事件而变化。在下一节,我们将引入一个新的概念:"state"。State使得React components可以根据用户的行为,网络响应或者其他什么,使得他们的输出每次不同,而不会违反此规则。