[note: 本文基于 React v15.3.0+ 进行讨论]
一般来说,编写 React 组件的方式有以下三种:
- 无状态组件(stateless component)。它是函数式的,不继承于任何类;
- 继承于
PureComponent
的组件; - 继承于
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
的优化。它会浅比较组件内部 props
和 state
的值,从而决定组件应不应该重新渲染,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()
就是容器组件的一种具体实现。