1.react机制和setState

1.react机制和setState

1.setState

参考:juejin.cn/post/688635…

组件中并没有实现setState的方法,为什么可以调用呢?

  • 原因很简单,setState方法是从Component中继承过来的。

为什么setState设计为异步呢?

  • setState设计为异步,可以显著的提升性能
    • 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;
    • 最好的办法应该是获取到多个更新,之后进行批量更新;
  • 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步;
    • state和props不能保持一致性,会在开发中产生很多的问题;

那么如何可以获取到更新后的值呢?

  • setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行;
  • 格式如下:setState(partialState, callback)

当然,我们也可以在生命周期函数:

componentDidUpdate(prevProps, provState, snapshot) {

console.log(this.state.message);

}

setState一定是异步?

  • 在组件生命周期或React合成事件中,setState是异步;
  • 在setTimeout或者原生dom事件中,setState是同步;

数据的合并

假如我们有这样的数据:

this.state = { name: "coderwhy", message: "Hello World" }

我们需要更新message:

  • 我通过setState去修改message,是不会对name产生影响的;

changeText() { this.setState({ message: "你好啊,李银河" }); }

为什么不会产生影响呢?源码中其实是有对 原对象 和 新对象进行合并的:

  • 事实上就是使用 Object.assign(target, ...sources) 来完成的


比如我们还是有一个counter属性,记录当前的数字:

  • 如果进行如下操作,那么counter会变成几呢?答案是1;
  • 为什么呢?因为它会对多个state进行合并;

increment() {

this.setState({ counter: this.state.counter + 1 });

this.setState({ counter: this.state.counter + 1 });

this.setState({ counter: this.state.counter + 1 });

}

其实在源码的processUpdateQueue中有一个do...while循环,就是从队列中取出多个state进行合并的;

多个对象值时,最好先copy出一个再去赋值

const { inputList } = this.state;

consttmpList = JSON.parse(JSON.stringify(inputList));

this.setState({ inputList:tmpList });

2.React更新机制

那么React的更新流程呢?

React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。

React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI:

  • 如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n 3 ),其中 n 是树中元素的数量;
  • 如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围;
  • 这个开销太过昂贵了,React的更新性能会变得非常低效;

于是,React对这个算法进行了优化,将其优化成了O(n),如何优化的呢?

  • 同层节点之间相互比较,不会垮节点比较;
  • 不同类型的节点,产生不同的树结构;
  • 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;

3. Diffing算法

1. 对比不同类型的元素

当节点为不同的元素,React会拆卸原有的树,并且建立起新的树:

  • 当一个元素从 a 变成 img,从 Article 变成 Comment,或从 Button 变成 div 都会触发一个完整的重建流程;
  • 卸载一棵树时,对应的DOM节点也会被销毁,组件实例将执行 componentWillUnmount() 方法;
  • 当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中,组件实例将执行componentWillMount() 方法,紧接着 componentDidMount() 方法

2. 对比同一类型的元素

当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。

如果是同类型的组件元素:

  • 组件会保持不变,React会更新该组件的props,并且调用componentWillReceiveProps() 和 componentWillUpdate() 方法;
  • 下一步,调用 render() 方法,diff 算法将在之前的结果以及新的结果中进行递归

3. 对子节点进行递归

默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个 mutation。

我们来看一下在最后插入一条数据的情况

  • 前面两个比较是完全相同的,所以不会产生mutation;
  • 最后一个比较,产生一个mutation,将其插入到新的DOM树中即可;
<li>first</li><li>second</li></ul><ul><li>first</li><li>second</li><li>third</li></ul>

但是如果我们是在中间插入一条数据:

  • React会对每一个子元素产生一个mutation,而不是保持
  • 星际穿越
  • 盗梦空间
  • 的不变;
  • 这种低效的比较方式会带来一定的性能问题;

4. keys的优化

我们在前面遍历列表时,总是会提示一个警告,让我们加入一个key属性

key的注意事项:

  • key应该是唯一的;
  • key不要使用随机数(随机数在下一次render时,会重新生成一个数字);
  • 使用index作为key,对性能是没有优化的;

5. SCU的优化

1. render函数被调用

嵌套案例:

  • 在App中,我们增加了一个计数器的代码;
  • 当点击+1时,会重新调用App的render函数;
  • 而当App的render函数被调用时,所有的子组件的render函数都会被重新

那么,我们可以思考一下,在以后的开发中,我们只要是修改了App中的数据,所有的组件都需要重新render,进行diff算法,性能必然是很低的:

  • 事实上,很多的组件没有必须要重新render;
  • 它们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法;

如何来控制render方法是否被调用呢?

  • 通过shouldComponentUpdate方法即可;

2. shouldComponentUpdate

React给我们提供了一个生命周期方法 shouldComponentUpdate(很多时候,我们简称为SCU),这个方法接受参数,并且需要有返回值:

  • 该方法有两个参数:
    • 参数一:nextProps 修改之后,最新的props属性
    • 参数二:nextState 修改之后,最新的state属性
  • 该方法返回值是一个boolean类型
    • 返回值为true,那么就需要调用render方法;
    • 返回值为false,那么久不需要调用render方法;
    • 默认返回的是true,也就是只要state发生改变,就会调用render方法;


shouldComponentUpdate(nextProps, nextState) {

return true;

}

我们可以控制它返回的内容,来决定是否需要重新渲染。

比如我们在App中增加一个message属性:

  • jsx中并没有依赖这个message,那么它的改变不应该引起重新渲染;
  • 但是因为render监听到state的改变,就会重新render,所以最后render方法还是被重新调用了;


这个时候,我们可以通过实现shouldComponentUpdate来决定要不要重新调用render方法:

  • 这个时候,我们改变counter时,会重新渲染;
  • 如果,我们改变的是message,那么默认返回的是false,那么就不会重新渲染

shouldComponentUpdate(nextProps, nextState) {

if (nextState.counter !== this.state.counter) {

return true;

}

return false;

}

3. PureComponent和memo

如果所有的类,我们都需要手动来实现 shouldComponentUpdate,那么会给我们开发者增加非常多的工作量。

我们来设想一下shouldComponentUpdate中的各种判断的目的是什么?

  • props或者state中的数据是否发生了改变,来决定shouldComponentUpdate返回true或者false;

事实上React已经考虑到了这一点,所以React已经默认帮我们实现好了,如何实现呢?

  • 将class基础自PureComponent。

PureComponent的原理是什么呢?

  • 对props和state进行浅层比较

那么,如果是一个函数式组件呢?

我们需要使用一个高阶组件memo:

  • 我们将之前的Header、Banner、ProductList都通过memo函数进行一层包裹;
  • Footer没有使用memo函数进行包裹;
  • 最终的效果是,当counter发生改变时,Header、Banner、ProductList的函数不会重新执行,而Footer的函数会被重新执行;

memo的原理是什么呢?

react/memo.js:

  • 最终返回一个对象,这个对象中有一个compare函数
免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部