🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 组件和 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 组件来修改它们的输出作为用户操作的响应、网络响应以及其它响应,而避免违反这条规则。