# 组件和 Props
组件使你单独的分离 UI 成为独立的、可复用的部分,并单独的思考每个部分。
从概念上讲,组件就像 JavaScript 函数。它们接受任意输入(称为 props)并返回 React 元素来描述什么应该出现在屏幕上。
## 功能组件和类组件
定义一个组件最简单的方式是编写一个 JavaScript 函数:
~~~
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
~~~
这个函数是一个有效的组件,因为它接受一个单独的 props 对象参数传递数据,并返回一个 React 元素。我们称折衷组件为 功能组件,因为它们是字面上的 JavaScript 函数。
你也可以使用一个[ ES6 的类](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes)来定义一个组件:
~~~
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
~~~
上面两个组件从 React 的角度来看是等效的。
类组件有一些额外的功能,我们将在下一节讨论。在那之前,我们将使用功能组件,因为它们更加简洁。
## 渲染一个组件
前面,我们只遇到了代表 DOM 标签的 React 元素:
~~~
const element = <div />;
~~~
然而,元素也可以代表用户定义的组件:
~~~
const element = <Welcome name="Sara" />;
~~~
当 React 发现一个表示用户定义的组件的元素时,它传递 JSX 属性到这个组件作为一个单独的对象。我们称这个对象为 props。
例如,这个代码渲染 "Hello, Sara" 到页面:
~~~
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
~~~
在 CodePen 中[打开它](http://codepen.io/gaearon/pen/YGYmEG?editors=0010)。
我们回顾一下这个例子中发生了什么:
* 调用 ReactDOM.render() ,传递 `<Welcome name="Sara" />` 元素
* React 调用 Welcome 组件,使用 {name: 'Sara'} 作为 props
* 我们的 Welcome 组件返回一个 `<h1>Hello, Sara</h1>`元素作为结果
* ReactDOM 有效的更新 DOM 来匹配 `<h1>Hello, Sara</h1>`
> 警告:
组件的名称总是以大写字母开始。例如 `<div />` 代表一个 DOM 标签,但是 `<Welcome />` 表示一个组件,需要在作用域内有一个 Welcome 。
## 组成组件
组件可以在它们的输出中借用其它组件。这使得我们使用同样的组件抽象任何的层级。一个按钮、一个表单、一个对话框、一个屏幕:在 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 中[打开它](http://codepen.io/gaearon/pen/KgQKPr?editors=0010)。
通常,新的 React apps 都有一个单独的顶层 App 组件。然而,如果你在已有的应用中继承 React,可能你从下至上从一个小的组件比如按钮,然后慢慢的到视图层级的顶层。
>[warning] 警告:
组件必须返回一个单独的根元素。这是为什么我们添加一个 `<div>` 来包含所有 `<Welcome />` 元素的原因。
## 提取组件
不用担心把一个组件分隔成更小的组件。例如,思考这个 Comment 组件:
~~~
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 中[打开它](http://codepen.io/gaearon/pen/VKQwEo?editors=0010)。
它接受 author(一个对象),text(一个字符串)和date(一个日期)作为属性,描述了一个社交媒体站点的评论。
这个组件修改起来很麻烦,因为它是被嵌套的,而且其中的独立部分难以复用。我们从其中提取一些组件。
首先,提取头像 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 中[打开查看](http://codepen.io/gaearon/pen/rrJNJY?editors=0010)。
提取组件可能看起来是一个繁琐的工作,但是在大型的 Apps 中可以回报给我们大量的可复用组件。一个好的经验准则是如果你 UI 的一部分需要用多次(按钮、面板、头像),或者本身足够复杂(App,FeedStory,Comment),好的做法是使其成为可复用组件。
## Props 和 只读
无论声明组件为一个函数或者一个类,它不能修改自己的 props。思考这个求和函数:
~~~
function sum(a, b) {
return a + b;
}
~~~
这种函数称为 “纯” 的,因为它们不会试图改变它们的输入,并且对于同样的输入总是返回相同结果。
作为对比,这个函数是“不纯”的,因为它改变了自己的输入:
~~~
function withdraw(account, amount) {
account.total -= amount;
}
~~~
React 相当灵活,但是它有一条严格的规则:
**所有 React 组件必须扮演纯函数,遵守它们的 props 。**
当然,应用 UIs 总是动态的,并且会一直改变。在下一节,我们将会介绍一个新的概念,关于 “state”。State (状态)允许 React 组件来修改它们的输出作为用户操作的响应、网络响应以及其它响应,而避免违反这条规则。