react异步setState简单实现

本文通过一个简单的示例探讨React中setState的异步行为。尽管setState函数本身是同步的,但由于其内部的处理机制,使得我们在实际使用中观察到的是异步效果。文章模拟了Component和setState过程,通过队列机制解释了如何实现异步更新,并展示了核心代码实现。

前言

直接主题,开始前先看一段demo:

class Demo{
    constructor(props) {
    super(props);
    this.state = {count: 0};
  }

  componentDidMount() {
    for(let i = 0 ;i < 10;i++) {
    
      // this.setState({
      //   count: this.state.count + 1
      // })
      
      // this.setState(() => {
      //   return {
      //     count: this.state.count + 1
      //   }
      // })
      
      // this.setState((prevState) => {
      //   return {
      //     count: prevState.count + 1
      //   }
      // })
      
      // console.log(this.state.count) // 0 * 10
    }
  }

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

在平常使用react的过程中,关于setState的使用有上面几种方式,那么他们的输出结果又有什么不一样?

熟悉的同学可以马上给出答案,分别是1, 1, 10

至于为什么,我们可以通过一段简单示例来了解下 setState 的过程。

我们常说 setState 是异步的,但实际上 setState 本身只是个同步函数,只是内部处理逻辑导致我们看到的结果是‘异步’的,这也是为什么当我们使用 console 去打印刚 setState 完的数据却发现没有变化的原因。当setSate不在react控制下的时候,setState就会同步更新state了,比如当用setTimeout包裹setState的时候。

正式开始

我们都知道setState为组件自带方法,而我们创建class组件的时候会继承react的Component,所以我们先模拟一个Component,以及函数setState,这个函数接受一个参数state,这个参数可以是对象也可以是方法,然后函数内部调用了一个state处理函数我们给它叫做setStateQueue,这个函数接受stateChange以及当前组件

class Component {
    constructor(props = {}) {
        this.props = props
        this.state = {}
    }

    setState(state) {
        setStateQueue(state, this)
    }
}

function setStateQueue(stateChange, comp) {
    // todo
}

setState方法通过一个队列机制实现state更新,我们需要定义个变量queue去保存这些更新, 这里我们还多加了个变量componentRenderQueue记录更新的组件

const queue = []
const componentRenderQueue = []

那么如何实现setState的异步呢?说到异步那就很容易想到setTimeout跟Promise
那么我们可以这么做,至于为什么可以了解下js的事件执行(event loop)。当当前更新队列为空的时候,延迟更新flush

function defer(fn) {
    if(Promise) {
        Promise.resolve().then(fn)
    } else {
        setTimeout(() => {
            fn()
        }, 0)
    }
}

function setStateQueue(stateChange, component) {
    // 将更新逻辑放至下个周期
   if(queue.length === 0) {
        defer(flush)
    }
}

每次执行setState()的时候,先将加入更新队列, 同时记录对应的组件

function setStateQueue(stateChange, component) {
   if(queue.length === 0) {
        defer(flush)
    }
    
    queue.push({
        stateChange,
        component
    })
    
    let componentExist = componentRenderQueue.some(item => {
        return item === component
    })
    
    if(!componentExist) {
        componentRenderQueue.push(component)
    }
}

前面的setState操作都结束了,那么就要开始真正的结果处理了. 依次执行更新队列中更新操作并记录,同时需要对上次的结果进行记录。当全都处理完成之后,对组件的state进行更新。具体逻辑如下:

function flush() {
    let item = queue.shift(),
        comp = componentRenderQueue.shift()

    // 记录每个组件的state修改状态
    const updater = {}

    while(item) {
        const {stateChange, component} = item
        const componentName = component.constructor.name

        if(!component.prevState) {
            component.prevState = Object.assign({}, component.state)
        }
        
        const newState = typeof stateChange === 'function' ? stateChange(component.prevState, component.props) : stateChange

        updater[componentName] = {
            component,
            prevState: newState
        }

        component.prevState = newState

        item = queue.shift()

    }

    for(let u in updater) {
        Object.assign(updater[u].component.state, updater[u].prevState)
    }

    while (comp) {
        // 接下去走组件渲染流程
    }
}

将以上代码整理合并后最终结果如下:

const queue = [] 
const componentRenderQueue = []

function defer(fn) {
    if(Promise) {
        Promise.resolve().then(fn)
    } else {
        setTimeout(() => {
            fn()
        }, 0)
    }
}

function setStateQueue(stateChange, component) {
    if(queue.length === 0) {
        defer(flush)
    }
    queue.push({
        stateChange,
        component
    })

    let componentExist = componentRenderQueue.some(item => {
        return item === component
    })
    if(!componentExist) {
        componentRenderQueue.push(component)
    }

}

// 更新state
function flush() {
    let item = queue.shift(),
        comp = componentRenderQueue.shift()

    const updater = {}

    while(item) {
        const {stateChange, component} = item
        const componentName = component.constructor.name

        if(!component.prevState) {
            component.prevState = Object.assign({}, component.state)
        }
        
        const newState = typeof stateChange === 'function' ? stateChange(component.prevState, component.props) : stateChange

        updater[componentName] = {
            component,
            prevState: newState
        }

        component.prevState = newState

        item = queue.shift()

    }

    for(let u in updater) {
        Object.assign(updater[u].component.state, updater[u].prevState)
    }

    while (comp) {
        // 接下去走组件渲染流程
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值