欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

React setState() 的原理解析

程序员文章站 2022-04-03 07:57:52
...

setState 的原理机制解析


我们本章节主要是要来分析一下React中常见的setState方法,熟悉React的小伙伴应该都知道,该方法通常用于改变组件状态并用新的state去更新组件。但是,这个方法在很多地方的表现总是与我们的预期不符,先来看几个案例。

常见案例

class Root extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  componentDidMount() {
    let that = this;

    that.setState({ count: that.state.count + 1 });
    console.log(that.state.count);    // 打印出 0

    that.setState({ count: that.state.count + 1 });
    console.log(that.state.count);    // 打印出 0

    setTimeout(function(){
     that.setState({ count: that.state.count + 1 });
     console.log(that.state.count);   // 打印出 2
    }, 0);

    setTimeout(function(){
     that.setState({ count: that.state.count + 1 });
     console.log(that.state.count);   // 打印出 3
    }, 0);

  }
  render() {
    return (
      <h1>{this.state.count}</h1>
    )
  }
}

这个案例的结果确实让人非常意外,打印出 0,0,2,3。为什么没有按照我们预期的1,2,3,4往下进行这样的打印方式呢?我们接着往下看:

在了解setState之前,我们先来简单了解下 React 一个包装结构: Transaction:

事务 (Transaction):

它呢,是 React 中的一个调用结构,用于包装一个方法,结构为: initialize - perform(thatthod) - close。通过事务,可以统一管理一个方法的开始与结束;处于事务流中,表示进程正在执行一些操作;

React setState() 的原理解析

设置状态值 setState:

ReactsetState用于修改状态,更新视图。它具有以下特点:

异步与同步: setState 并不是单纯的异步或同步,这其实与调用时的环境相关:

  • 在 合成事件 和 生命周期钩子(除 componentDidUpdate ) 中,setState是"异步"的;

原因:

因为在setState的实现中,有一个判断: 当更新策略正在事务流的执行中时,该组件更新会被推入dirtyComponents队列中等待执行;否则,开始执行batchedUpdates队列更新;

  • 在生命周期钩子调用中,更新策略都处于更新之前,组件仍处于事务流中,而componentDidUpdate是在更新之后,此时组件已经不在事务流中了,因此则会同步执行;
  • 在合成事件中,React 是基于 事务流完成的事件委托机制 实现,也是处于事务流中;

React setState() 的原理解析

  • 问题: 无法在setState后马上从this.state上获取更新后的值。
    • 解决: 如果需要马上同步去获取新值,setState其实是可以传入第二个参数的。setState(updater, callback),在回调中即可获取最新值;
	setState({
    	index: 1
	}}, function(){
    	console.log(this.state.index);
	})
* 在钩子函数中体现:
	componentDidUpdate(){
	    console.log(this.state.index);
	}
  • 在 原生事件 和 setTithatout 中,setState 是同步的,可以马上获取更新后的值;

    • 原因: 原生事件是浏览器本身的实现,与事务流无关,自然是同步;而setTithatout是放置于定时器线程中延后执行,此时事务流已结束,因此也是同步;
  • 批量更新: 在 合成事件生命周期钩子 中,setState更新队列时,存储的是 合并状态(Object.assign)。因此前面设置的 key 值会被后面所覆盖,最终只会执行一次更新;如下图所示:

React setState() 的原理解析

  • 函数式: 由于 Fiber 及 合并 的问题,官方推荐可以传入 函数 的形式。setState(fn),在fn中返回新的state对象即可,例如this.setState((state, props) => newState);使用函数式,可以用于避免setState的批量更新的逻辑,传入的函数将会被 顺序调用
class Com extends Component{
    constructor(props){
        super(props);
        this.state = { index: 0 };
        this.add = this.add.bind(this);
    }

    add(){
        this.setState(prevState => {
            return {index: prevState.index + 1};
        });
        this.setState(prevState => {
            return {index: prevState.index + 1};
        });
    }
}

注意事项:

  1. setState 合并,在 合成事件生命周期钩子多次连续调用会被优化为一次

  2. 当组件已被销毁,如果再次调用setStateReact 会报错警告,通常有两种解决办法:

    • 将数据挂载到外部,通过 props 传入,如放到 Redux 或 父级中;
    • 在组件内部维护一个状态量(isUnmounted)componentWillUnmount中标记为 true,在setState前进行判断;
相关标签: react