React 组件的三种形式

[note: 本文基于 React v15.3.0+ 进行讨论]

一般来说,编写 React 组件的方式有以下三种:

  1. 无状态组件(stateless component)。它是函数式的,不继承于任何类;
  2. 继承于 PureComponent 的组件;
  3. 继承于 Component 的组件,这也是最常使用的组件形式。

那么,这三种形式的使用场景及优劣各是什么呢?

无状态组件

无状态组件是通过函数定义的,比如下面这个最简单的?:

let Hello = (props) => <div>Hello {props.name}</div>

调用的时候,和平常组件的使用方式相同:

<Hello name="cjf" />

可以看出,无状态组件的最大特征就是没有内部状态(废话),所以无状态组件的渲染结果完全取决于输入的 props

无状态组件的优点有以下几个方面:

  • 简单;
  • 可复用性高。因为无状态组件不包含内部状态,也就是没有内在逻辑,输出完全取决于输入,所以复用组件只需要输入不同的数据即可;
  • 单元测试更容易进行。因为逻辑都被移出了 view 层,所以单元测试时不需要渲染任何东西,可以专注于单个逻辑。

缺点有以下两点:

  • 无状态组件中不能使用生命周期函数。如果一定要用的话,只能在外面包一层父组件,然后定义生命周期函数;
  • 无法手动控制无状态组件的重新渲染(re-render)。只要无状态组件接收到了新的 props,它就会进行重新渲染。当然还有一点需要注意的是,无状态组件对于 props 是浅比较的。

继承于 PureComponent 的组件

这类组件的定义方式如下:

class MyComponent extends PureComponent {...}

PureComponent 是继承于 Component 类的,不同的是内部实现了 shouldComponentUpdate 的优化。它会浅比较组件内部 propsstate 的值,从而决定组件应不应该重新渲染,PureComponent 的使用可以提高 React 应用的性能。

PureComponent 中,我们不需要写类似于

if (this.state.someVal !== computedVal) {
  this.setState({ someVal: computedVal })
}

的代码来避免组件被重复渲染。

让我们看看源码
当一个组件是 PureComponent 时,会执行下面操作来检查是否需要 update

if (type.prototype && type.prototype.isPureReactComponent) {
  return (
    !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
  );
}

这里用的也是浅比较(shallowEqual),所以对于多层嵌套的对象和数组中的变化都不会被检测到。比如下面这个例子:

handleClick() {
  let { items } = this.state

  items.push('new-item')
  this.setState({ items })
}

render() {
  return (
    <div>
      <button onClick={::this.handleClick} />
      <ItemList items={ this.state.items } />
    </div>
  )
}

假设 ItemList 是一个 PureComponent,因为它执行的是浅比较,所以 this.state.items 的变化是不会触发 ItemList 的更新的。如果要触发更新,应该使用 setState 的另一种调用形式,每次都返回一个新的对象:

handleClick() {
  this.setState(prevState => ({
    words: prevState.items.concat(['new-item'])
  }));
}

最后需要注意的是,在 PureComponent 中是不能自己定义的 shouldComponentUpdate() 方法的。如果实在有这个必要,只能使用 Component 组件。

继承于 Component 的组件

没啥说的,这就是最普通的组件形式。

后续

从上面的讨论我们可以得出结论,在 React 应用中,应当尽可能多的使用无状态组件或 PureComponent 以增强复用性和提高性能。但是,在具体的项目实践中,我们往往需要通过 Ajax 请求获取数据,并进一步对数据进行处理。为了使组件的职责更加单一,引入了容器组件(Container Component)的概念。我们可以将数据获取以及处理的逻辑放在容器组件中,然后将已处理的数据传递给展示组件,使得组件的耦合性进一步地降低。

react-redux 中的 connect() 就是容器组件的一种具体实现。

参考文献:

  1. https://stackoverflow.com/que…
  2. https://facebook.github.io/re…
  3. https://github.com/eyasliu/bl…
  4. https://juejin.im/post/596d65…
  5. http://www.zcfy.cc/article/re…
  6. https://60devs.com/pure-compo…
  7. http://www.jianshu.com/p/980a…
    原文作者:言十八
    原文地址: https://segmentfault.com/a/1190000011175204
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞