• 组件 & Props
    • 函数组件与 class 组件
    • 渲染组件
    • 组合组件
    • 提取组件
    • Props 的只读性

    组件 & Props

    组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。本指南旨在介绍组件的相关理念。你可以参考详细组件 API。

    组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

    函数组件与 class 组件

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

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

    该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

    你同时还可以使用 ES6 的 class 来定义组件:

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

    上述两个组件在 React 里是等效的。

    我们将在下一章节中讨论关于 class 的额外特性。在那之前,为了保持示例简单明了,将使用函数组件。

    渲染组件

    之前,我们遇到的 React 元素都只是 DOM 标签:

    1. const element = <div />;

    不过,React 元素也可以是用户自定义的组件:

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

    当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)转换为单个对象传递给组件,这个对象被称之为 “props”。

    例如,这段代码会在页面上渲染 “Hello, Sara”:

    1. function Welcome(props) {
    2. return <h1>Hello, {props.name}</h1>;
    3. }
    4. const element = <Welcome name="Sara" />;
    5. ReactDOM.render(
    6. element,
    7. document.getElementById('root')
    8. );

    在 CodePen 上尝试

    让我们来回顾一下这个例子中发生了什么:

    • 我们调用 ReactDOM.render() 函数,并传入 <Welcome name="Sara" /> 作为参数。
    • React 调用 Welcome 组件,并将 {name: 'Sara'} 作为 props 传入。
    • Welcome 组件将 <h1>Hello, Sara</h1> 元素作为返回值。
    • React DOM 将 DOM 高效地更新为 <h1>Hello, Sara</h1>

    注意: 组件名称必须以大写字母开头。

    React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div /> 代表 HTML 的 div 标签,而 <Welcome /> 则代表一个组件,并且需在作用域内使用 Welcome

    你可以在深入 JSX中了解更多关于此规范的原因。

    组合组件

    组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。

    例如,我们可以创建一个可以多次渲染 Welcome 组件的 App 组件:

    1. function Welcome(props) {
    2. return <h1>Hello, {props.name}</h1>;
    3. }
    4. function App() {
    5. return (
    6. <div>
    7. <Welcome name="Sara" />
    8. <Welcome name="Cahal" />
    9. <Welcome name="Edite" />
    10. </div>
    11. );
    12. }
    13. ReactDOM.render(
    14. <App />,
    15. document.getElementById('root')
    16. );

    在 CodePen 上尝试

    通常来说,每个新的 React 应用程序的顶层组件都是 App 组件。但是,如果你将 React 集成到现有的应用程序中,你可能需要使用像 Button 这样的小组件,并自下而上地将这类组件逐步应用到视图层的每一处。

    提取组件

    将组件拆分为更小的组件。

    例如,参考如下 Comment 组件:

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

    在 CodePen 上尝试

    该组件用于描述一个社交媒体网站上的评论功能,它接收 author(对象),text (字符串)以及 date(日期)作为 props。

    该组件由于嵌套的关系,变得难以维护,且很难复用它的各个部分。因此,让我们从中提取一些组件出来。

    首先,我们将提取 Avatar 组件:

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

    Avatar 不需知道它在 Comment 组件内部是如何渲染的。因此,我们给它的 props 起了一个更通用的名字:user,而不是 author

    我们建议从组件自身的角度命名 props,而不是依赖于调用组件的上下文命名。

    我们现在针对 Comment 做些微小调整:

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

    接下来,我们将提取 UserInfo 组件,该组件在用户名旁渲染 Avatar 组件:

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

    进一步简化 Comment 组件:

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

    在 CodePen 上尝试

    最初看上去,提取组件可能是一件繁重的工作,但是,在大型应用中,构建可复用组件库是完全值得的。根据经验来看,如果 UI 中有一部分被多次使用(ButtonPanelAvatar),或者组件本身就足够复杂(AppFeedStoryComment),那么它就是一个可复用组件的候选项。

    Props 的只读性

    组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。来看下这个 sum 函数:

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

    这样的函数被称为“纯函数”,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。

    相反,下面这个函数则不是纯函数,因为它更改了自己的入参:

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

    React 非常灵活,但它也有一个严格的规则:

    所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

    当然,应用程序的 UI 是动态的,并会伴随着时间的推移而变化。在下一章节中,我们将介绍一种新的概念,称之为 “state”。在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。