今天我们解决以下几个问题,什么是immutable data,mutable data带来了哪些问题,immutable data优化了哪些性能?
mutable data 数据的可变性
数据的可变性用一段代码就可以描述清楚
const a = [{ todo: 'Learn js'}, {todo: 'Learn react'}];
const b = [...a];
b[1].todo = 'Learn vue';
console.log(a[1].todo); //Learn vue
其实可以一眼看出这是浅copy导致的问题。内层的对象指向堆内存地址相同,所以修改b数组中的对象,a数组也会发生变化。平时大伙在项目中操作比较复杂的数据结构时,都习惯性deepCopy,否则就会出现一些不易察觉的bug。
immutable数据优化了哪些性能
首先我们要看一下React 是如何渲染的。
React 渲染机制解析
graph LR
setState或者props改变 --> shouldComponentUpdate --true --> 递归render 
递归render --> componentDidUpdate 
在React中,render函数返回虚拟dom树,并经过Diff算法计算出与上次虚拟dom的区别,针对差异的部分做更新,渲染出真实dom。
递归render的过程是性能消耗的大头,如果shouldComponentUpdate返回false,更新的过程就会被打住,所以我们要好好的利用这个shouldComponentUpdate。
shouldComponentUpdate
这是一个组件的子树。每个节点中,SCU 代表 shouldComponentUpdate 返回的值,而 vDOMEq 代表返回的 React 元素是否相同。最后,圆圈的颜色代表了该组件是否需要被调停,红色代表shouldComponentUpdate返回true,进行render,绿色代表返回false,不进行render。

c1是红色节点,shouldComponentUpdate返回 true,进入diff算法比对新旧VDom树,如果新旧VDom树中节点类型不同,则全部替换,包括下面子组件,图中展示的是节点类型相同的情况,则递归子组件。
//什么是节点类型不同
<A>
  <C/>
</A>
// A与B是不同节点类型
<B>
  <C/>
</B>
React会直接删掉A节点(包括它所有的子节点),然后新建一个B节点插入。
节点 C2 的 shouldComponentUpdate 返回了 false,React 因而不会调用 C2 的 render,也因此 C4 和 C5 的 shouldComponentUpdate 不会被调用到。
C3,shouldComponentUpdate 返回了 true,所以 React 需要继续向下查询子节点。这里 C6 的 shouldComponentUpdate 返回了 true,同时由于渲染的元素与之前的不同使得 React 更新了该 DOM。
最后一个有趣的例子是 C8。React 需要渲染这个组件,但是由于其返回的 React 元素和之前渲染的相同,所以不需要更新 DOM。
显而易见,你看到 React 只改变了 C6 的 DOM。对于 C8,通过对比了渲染的 React 元素跳过了渲染。而对于 C2 的子节点和 C7,由于 shouldComponentUpdate 使得 render 并没有被调用。因此它们也不需要对比元素了。
类组件React.PureComponent与函数组件memo
通过shouldComponentUpdate可以避免不必要的渲染过程,从而达到性能上的优化。但是如果需要我们挨个对比props和state中的每个属性的话就太麻烦了,React提供了两种方式自动帮我们完成shouldComponentUpdate中的工作,类组件只要继承React.PureComponent就可以,函数组件提供了memo方法。
//三种方式
class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }
  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }
  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}
class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }
  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}
const CounterButton = props => {
  const [count, setCount] = useState(1);
  return (
    <button color={props.color} onClick={() => setCount(count => count + 1)}>
      Count: {count}
    </button>
  );
};
export default React.memo(CounterButton);
那有没有一种方式,使用浅比较就可以得出哪部分是改变的数据节点呢?
immutable 数据结构
我们还是通过一小段代码来认识什么是immutable数据,这里使用的是Immer这个库。
  import produce from 'immer';
  
  const a = [{ todo: 'Learn js' }, { todo: 'Learn react' }];
  const b = produce(a, draftState => {
    draftState[1].todo = 'Learn vue';
  });
  console.log(a === b); //false
  console.log(a[0] === b[0]); //true
  console.log(a[1] === b[1]); //false
  console.log(a[1].todo === b[1].todo); //false
这里可以看到未改变的引用类型内存地址未发生改变,保证了旧节点的可用且不变,而改变了的节点,它和与它相关的所有上级节点都更新。如图所示:

这样就避免了深拷贝带来的极大的性能开销问题,并且更新后返回了一个全新的引用,即使是浅比对也能感知到哪一部分数据需要更新。
immer应用示例
const [state, setState] = useState({
    id: 14,
    email: "stewie@familyguy.com",
    profile: {
      name: "Stewie Griffin",
      bio: "You know, the... the novel you've been working on",
      age:1
    }
  });
function changeBio(newBio) {
    setState(current => ({
      ...current,
      profile: {
        ...current.profile,
        bio: newBio
      }
    }));
  }
//使用 immer
import { useImmer } from 'use-immer';
const [state, setState] = useImmer({
    id: 14,
    email: "stewie@familyguy.com",
    profile: {
      name: "Stewie Griffin",
      bio: "You know, the... the novel you've been working on",
      age:1
    }
 });
function changeBio(newBio) {
   setState(draft => {
      draft.profile.bio = newBio;
    });
  }
减少了解构语法是不是清爽了很多,当然随着数据结构进一步复杂,immer优势也会进一步体现。
感谢大家的阅读。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
 
                     
     
        
       
        
       
        
       
        
       
    
发表评论
还没有评论,快来抢沙发吧!