函数式编程是指程序里面的函数和表达式都能像数学中的函数一样,给定了输入值,输出是确定的。比如

  1. const a = 1;
  2. function addOne(num) {
  3. return num + 1;
  4. }
  5. const b = addOne(a);

变量b出现,虽然使用了变量a的值,但是没有修改a的值,此时 a 依然等于 1;
再看react中的代码,假如初始化了this.state = { count: 1 }

  1. class ReactDemo extends React.Component {
  2. state = { count: 1 };
  3. componentDidMount() {
  4. this.setState({ ...state, count: 2 }); // { count: 2 }
  5. }
  6. }

这里我们使用了this.state,但是没有修改 this.state 的引用地址和原始引用中的count值,保证了数据的不可变性;

什么是Immutable

Immutable值具有以下特性:

  • Immutable 是一旦创建,就不能被更改的数据;
  • 对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象,保证旧数据同时可用且不变;

Immutable 实现采用了持久化数据结构(Persistent Data Structure),为了避免深拷贝把所有节点都复制一遍带来的性能损耗,Immutable 使用了结构共享(Structural Sharing),即如果对象树结点发生变化,只修改这个结点和受它影响的父节点,其他结点进行共享。

使用 Immutable 带来的好处正如 React 官网所说:

  • 简化复杂的功能

    如果直接修改数据,那么就很难跟踪到数据的改变。跟踪数据的改变需要可变对象可以与改变之前的版本进行对比,这样整个对象树都需要被遍历一次

  • 跟踪数据的改变

    如果发现对象变成了一个新对象,那么我们就可以说对象发生改变了。

React 中的 Immutable Data

幼儿园小朋友都知道,shouldComponentUpdate 钩子函数默认返回true,即只要父组件更新,子组件一定更新。React v15.3新增加了一个PureComponent类,能够对props和state进行浅比较来减少render函数的执行次数,避免不必要的组件渲染,实现性能上的优化。

注:对于函数组件,可使用 React.memo(YourFunctionComponent) 实现同样的性能优化

可是 PureComponent 是什么原理呢?
我们知道JS中的变量类型分为基本类型(number、string、boolean、undefined、null、symbol)和引用类型(object),基本类型的值保存在栈内存当中,引用类型的值保存在堆内存当中,栈内存中只保存指向堆内存的引用。而浅比较就是只对栈内存中的数据进行比较。实现一个简单的浅比较就好理解了

  1. function isDifferent(prev, next) {
  2. const prevKeys = Object.keys(prev);
  3. const nextKeys = Object.keys(next);
  4. if(nextKeys.length !== prevKeys.length) {
  5. return true;
  6. }
  7. return !prevKeys.every(key => Object.is(prev[key], next[key]));
  8. }

可以发现,这个比较并没有递归全部数据,可是平衡性能的一个结果。
这样,PureComponent 可以简单写为:

  1. class PrueComponent extends React.Component {
  2. shouldComponent(nextProps, nextState) {
  3. return isDifferent(this.props, nextProps) || isDifferent(this.state, nextState);
  4. }
  5. }

这里提供一个使用 PureComponent 时 setState 无法触发渲染的反例,来理解一下上述功能:

  1. class App extends PureComponent {
  2. state = {
  3. items: [1, 2]
  4. }
  5. componentDidMount() {
  6. const { items } = this.state;
  7. items.pop();
  8. this.setState({ items });
  9. }
  10. render() {
  11. return this.state.items.map(num => <div>{num}</div>);
  12. }
  13. }

上边这个例子使用了 PureComponent,而且只改变了数组items里的值,而没改变 items 的引用地址,所以认为 items 没有发生变化,不会触发 render 函数,不会触发组件的渲染。所以 PureComponent 最好是搭配 Immutable.js 进行使用,来达到性能优化的目的。