【译】更好地设计 React 组件

原文连接:React Component Patternshtml

github 的地址 欢迎 starnode

前言

在我为一次会议准备技术演讲的过程当中,我想花点时间和你们分享我在设计 React 组件模式上一些感悟。组件是 React 的核心,理解它对于设计一个良好的项目架构是十分重要的。react

文中的图表是从一个很是棒的演讲(来自 Michael Chan)获得的。我强烈推荐去看他的视频git

什么是组件

根据 React 官网的介绍,组件可让你把 UI 划分为独立,可复用的部件,你只需独立的考虑每一个部件的构建设计。github

当你第一次运行 npm install react,在本地就能够获取到 React 源码(路径./node_modules/react/umd/react.development.js),它能够看作一个大的组件,提供了一系列的接口。React 组件和 JavaScript 函数是相似,组件接受一个称为 “props” 的输入,返回描述(声明)用户界面的 React 元素。你只须要告诉 React 你用户界面的样子,React 就会帮你把剩下的事情完成(保持 DOM 和数据同步),渲染出界面。这也是 React 被称为声明式库的缘由。npm

声明式就是假如你要去一个地方的时候,你选择了打的,只须要告诉司机你的目的地,司机就会本身带你到目的地。而命令式是相反的,是须要你本身驾车去目的地的。redux

组件的 API

那么,当你下载 React,获得了哪些 API 呢?它们有5个:设计模式

  • render
  • state
  • props
  • context
  • lifecycle events

虽然组件提供了一份完整,方便利用的 API,但很天然的一些组件,你会使用一部分 API,另外的组件使用和以前不彻底相同的API。通常就把组件划分为有状态 (Stateful) 组件和无状态 (stateless) 组件。有状态组件一般用到了 render, state以及( lifecycle events)生命周期钩子,无状态组件一般使用了 render, props以及contextbash

这里就已经涉及到 React 组件的一些设计模式。好的设计模式是可以清晰的拆分数据或逻辑层以及用户界面(展现)层的最佳实践。通常一个较大的组件,你能够按功能职责再进行拆分,这样就能实现更多可复用的,高内聚的组件,能够很方便用它们构建复杂的用户界面。这在构建可扩展的应用程序时尤其重要。

组件设计模式

一般的设计模式有:react-router

  • 容器 (Container) 组件
  • 展现 (Presentational) 组件
  • 高阶 (Higher-Order) 组件
  • 渲染回调(Render Callbacks)

容器 (Container) 组件

容器组件是同外部数据进行交互(通讯),而后渲染其相应的子组件 --Jason Bonta

蓝色的表明容器组件,其里面灰的表示展现组件

容器组件是数据或逻辑层,你可以使用上面提到的有状态的 API。使用生命周期钩子,能直接链接到状态管理 store,例如 Redux 或 Flux,能经过 props 传递数据和回调给其相应的子组件。 容器组件的 render 方法中返回的是由多个展现子组件组成的 React 元素。为了能访问全部的有状态的 API,容器组件必须用 ES6 的 class 声明组件,而不是用函数声明。

以下,声明了叫 Greeting 的组件,它有 state, 一个生命周期钩子 componentDidMount() 以及 render

class Greeting extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "",
    };
  }

  componentDidMount() {
    // AJAX
    this.setState(() => {
      return {
        name: "William",
      };
    });
  }

  render() {
    return (
      <div>
        <h1>Hello! {this.state.name}</h1>
      </div>
    );
  }
}
复制代码

此时,这个组件是一个有状态的组件。为了使 Greeting组件变成容器组件,能够将用户界面拆分为展现组件,将在下面说明。

展现(Presentational)组件

展现组件可以使用props, render以及 context(无状态的 API),它其实就是能够用函数声明的无状态组件:

const GreetingCard = (props) => {
  return (
    <div>
      <h1>Hello! {props.name}</h1>
    </div>
  )
}
复制代码

展现组件仅仅从 props 中接受数据和回调,props 是由容器组件或者它的父组件产生的。

蓝色的表明展现组件,灰色的表示容器组件 用容器和展现组件分别同时地封装了逻辑与 UI 展现,这样才能获得理想的组件:

const GreetingCard = (props) => {
  return (
    <div>
      <h1>{props.name}</h1>
    </div>
  )
}

class Greeting extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "",
    };
  }

  componentDidMount() {
    // AJAX
    this.setState(() => {
      return {
        name: "William",
      };
    });
  }

  render() {
    return (
      <div>
       <GreetingCard name={this.state.name} />
      </div>
    );
  }
}
复制代码

如上面所见,我把UI展现的部分从 Greeting移动到了一个函数式无状态组件。固然这只是一个简单的例子,但在更复杂的应用中基本上也是这么处理的。

高阶组件(HOC)

高阶组件就是一个函数,且该函数接受一个组件做为参数,并返回一个新的组件。

这是一种为任意组件复用某个组件逻辑而提供的强大模式。就好比react-router-v4 和 Redux。在react-router-v4中,使用withRouter(),你的组件就能经过 props 继承react-router中的一些方法。在 redux 中也是同样的,connect({})() 方法就能把 actionsreducer 传入组件中。

高级组件用上图的虚线表示,它是一个函数返回了一个新的组件

来看这个例子:

import {withRouter} from 'react-router-dom';

class App extends React.Component {
  constructor() {
    super();
    this.state = {path: ''}
  }
  
  componentDidMount() {
    let pathName = this.props.location.pathname;
    this.setState(() => {
      return {
        path: pathName,
      }
    })
  }
  
  render() {
    return (
      <div>
        <h1>Hi! I'm being rendered at: {this.state.path}</h1> </div> ) } } export default withRouter(App); 复制代码

当导出个人组件的时候,用 react-router-v4 的 withRouter() 包裹了个人组件。而后在生命周期钩子 componentDidMount() 中,能够经过 this.props.location.pathname 中的值更新 state。个人组件就经过 props 获取到了 react-router-v4 的方法。还有不少其余的例子。

Render callbacks(渲染回调)

与 HOC 相似的,render callbacks 或 render props也是共享或复用组件逻辑的强大模式。尽管更多的开发者倾向于经过 HOC 复用逻辑,使用 render callbacks 仍是有必定缘由和优点的--在一次 Michael Jackson “毫不再写另外一个的HOC”极好的解释了。其中涉及到一些关键的地方,render callbacks 可以减小命名空间的冲突以及更好地说明代码逻辑是来自哪里。

蓝色的虚线表明 render callbacks

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  increment = () => {
    this.setState(prevState => {
      return {
        count: prevState.count + 1,
      };
    });
  };

  render() {
    return (
      <div onClick={this.increment}>{this.props.children(this.state)}</div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <Counter>
        {state => (
          <div>
            <h1>The count is: {state.count}</h1>
          </div>
        )}
      </Counter>
    );
  }
}
复制代码

Count 组件的 render 中嵌套了 this.props.children 方法,并把 this.state 做为参数传给它。在 App 组件中,我用 Counter 包裹了它,在 App 中就能够获取到 Counter 的数据方法等逻辑。{state => ()} 就是 render callback。我自动地获取到了 Counter 中的 state。

谢谢你的阅读

欢迎你们留言建议,以上就是我对 React 组件设计模式的见解!

上面没有提到 render props,能够查看官网介绍例子

固然 React V16.8.0 添加了 hooks新的API,用函数也能实现有状态的组件了,你们能够查看官网了解

最后,推荐你们关注 React 做者之一 Dan 的博客,编写有弹性的组件的4个原则。

若是有错误或者不严谨的地方,请务必给予指正,十分感谢!

参考

  1. levelup.gitconnected.com/react-compo…
  2. reactjs.org/
相关文章
相关标签/搜索