Giter VIP home page Giter VIP logo

Comments (7)

oliver1204 avatar oliver1204 commented on May 3, 2024 3
this.setState((state) => {
   console.log(state.val)   // 0
   return { val: state.val + 1 }
})
       
this.setState((state) => {
   console.log(state.val)   // 1
   return { val: state.val + 1 }
})

上面的代码之所以可以说实现0,1 效果,其原理如下:

class Component {
  constructor() {
    this.updateQueue = [];
    this.callbackQueue = [];
    this.isBatchingUpdates = false;

    this.state = {
      val: 0
    };
  }
  setState(partialState, callback) {
    if(this.isBatchingUpdates) {
      this.updateQueue.push(partialState)
      this.callbackQueue.push(callback)
    }
  }
  add() {
    this.isBatchingUpdates = true
    this.setState((preState) => ({val: preState.val + 1}), () => {
      console.log(this.state.val)
    })
    this.setState((preState) => ({val: preState.val + 1}), () => {
      console.log(this.state.val)
    })
    this.setState((preState) => ({val: preState.val + 1}), () => {
      console.log(this.state.val)
    })
    this.flushQueue()
  }
  flushQueue() {
    let partialState = this.updateQueue.reduce((pre, next) => { 
        return next(pre)
    }, this.state)

    this.state = {...this.state, ...partialState}

    this.callbackQueue.map(callback => callback())
    this.isBatchingUpdates = false;
  }
}

let c = new Component()
c.add()

from react.

hehe1111 avatar hehe1111 commented on May 3, 2024

Dan 老哥的回答

1. 保证内部的一致性

即使state是同步更新,props也不是。(你只有在父组件重新渲染时才能知道props)

2. 性能优化

将state的更新延缓到最后批量合并再去渲染对于应用的性能优化是有极大好处的,如果每次的状态改变都去重新渲染真实dom,那么它将带来巨大的性能消耗。

原理解释

面试官:“react中setState是同步的还是异步?”
我:“异步的,setState不能立马拿到结果。”

面试官:“那什么场景下是异步的,可不可能是同步,什么场景下又是同步的?”
我:“......”

setState并不是真正意义上的异步操作,它只是模拟了异步的行为

为什么这么说。可以通过下面的例子
不是真正意义上的异步操作

class App extends Component {
  state = {
    count: 0
  };

  componentDidMount() {
    // 生命周期中调用
    this.setState({ count: this.state.count + 1 });
    console.log("lifecycle: " + this.state.count);
    setTimeout(() => {
      // setTimeout中调用
      this.setState({ count: this.state.count + 1 });
      console.log("setTimeout: " + this.state.count);
    }, 0);
    document.getElementById("div2").addEventListener("click", this.increment2);
  }

  increment = () => {
    // 合成事件中调用
    this.setState({ count: this.state.count + 1 });
    console.log("react event: " + this.state.count);
  };

  increment2 = () => {
    // 原生事件中调用
    this.setState({ count: this.state.count + 1 });
    console.log("dom event: " + this.state.count);
  };

  render() {
    return (
      <div className="App">
        <h2>couont: {this.state.count}</h2>
        <div id="div1" onClick={this.increment}>
          click me and count+1
        </div>
        <div id="div2">click me and count+1</div>
      </div>
    );
  }
}

探讨前,我们先简单了解下react的事件机制:react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClickonChange这些都是合成事件。

那么以上4种方式调用setState(),后面紧接着去取最新的state,按之前讲的异步原理,应该是取不到的。然而,setTimeout中调用以及原生事件中调用的话,是可以立马获取到最新的state的。根本原因在于,setState并不是真正意义上的异步操作,它只是模拟了异步的行为。React中会去维护一个标识(isBatchingUpdates),判断是直接更新还是先暂存state进队列。setTimeout以及原生事件都会直接去更新state,因此可以立即得到最新state。而合成事件和React生命周期函数中,是受React控制的,其会将isBatchingUpdates设置为 true,从而走的是类似异步的那一套。

在 setTimeout 中去 setState 并不算是一个单独的场景,它是随着你外层去决定的,因为你可以在合成事件中 setTimeout ,可以在钩子函数中 setTimeout ,也可以在原生事件setTimeout,但是不管是哪个场景下,基于event loop的模型下, setTimeout 中里去 setState 总能拿到最新的state值。

源码分析

总结

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
  2. setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

对于3 可以结合下面的例子:

class App extends React.Component {
  state = { val: 0 }

  componentDidMount() {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)

    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)

    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val);

      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)
    }, 0)
  }

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

结合上面分析的,钩子函数中的 setState 无法立马拿到更新后的值,所以前两次都是输出0,当执行到 setTimeout 里的时候,前面两个state的值已经被更新,由于 setState 批量更新的策略, this.state.val 只对最后一次的生效,为1,而在 setTimmout 中 setState 是可以同步拿到更新结果,所以 setTimeout 中的两次输出2,3,最终结果就为 0, 0, 2, 3 。

想一下,如何将上面的代码中前两次连续 + 1 都执行呢?

class App extends React.Component {
  state = { val: 0 }

  componentDidMount() {
    this.setState((state) => {
        console.log(this.state.val)   // 0
        return { val: this.state.val + 1 }
    })
     
    this.setState((state) => {
        console.log(this.state.val)   // 1
        return { val: this.state.val + 1 }
    })

    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val);  // 3

      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)  // 4
    }, 0)
  }

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

最后一段,运行结果还是 0 0 2 3,并不是 0 1 3 4

from react.

yibingxiong avatar yibingxiong commented on May 3, 2024

楼上说的对 , 运行结果还是 0 0 2 3,并不是 0 1 3 4, 楼主大佬解释下呗

from react.

yibingxiong avatar yibingxiong commented on May 3, 2024

改成这样就对了

componentDidMount() {
      this.setState((state) => {
          console.log(state.val)   // 0
          return { val: state.val + 1 }
      })
       
      this.setState((state) => {
          console.log(state.val)   // 1
          return { val: state.val + 1 }
      })
  
      setTimeout(_ => {
        this.setState({ val: this.state.val + 1 })
        console.log(this.state.val);  // 3
  
        this.setState({ val: this.state.val + 1 })
        console.log(this.state.val)  // 4
      }, 0)
    }

from react.

DangoSky avatar DangoSky commented on May 3, 2024

改成这样就对了

componentDidMount() {
      this.setState((state) => {
          console.log(state.val)   // 0
          return { val: state.val + 1 }
      })
       
      this.setState((state) => {
          console.log(state.val)   // 1
          return { val: state.val + 1 }
      })
  
      setTimeout(_ => {
        this.setState({ val: this.state.val + 1 })
        console.log(this.state.val);  // 3
  
        this.setState({ val: this.state.val + 1 })
        console.log(this.state.val)  // 4
      }, 0)
    }

@yibingxiong

因为第二个setState里this.state的数据还不是最新的,此时this.state.val还是0。而参数state表示上一次更新后的state,是最新的,state.val是1。

可以参考这篇blog:https://blog.csdn.net/Mr_28/article/details/84778001

from react.

yibingxiong avatar yibingxiong commented on May 3, 2024

@DangoSky 优秀

from react.

oliver1204 avatar oliver1204 commented on May 3, 2024
componentDidMount() {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)

    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)

   .....
  }

对于上面一段代码为什么会是0,0 ,笔者将源码缩略后,原理如下:

class Component {
  constructor() {
    this.updateQueue = [];
    this.isBatchingUpdates = false;  // 是否需要暂存

    this.state = {
      val: 0
    };
  }
  setState(newState) {
    if(this.isBatchingUpdates) {
      this.updateQueue.push(newState)
    }
  }
  add() {
    this.isBatchingUpdates = true
    this.setState({number: this.state.val + 1}) //   this.state.val = 0
    this.setState({number: this.state.val + 1})  //   this.state.val = 0
    this.setState({number: this.state.val + 1})  //   this.state.val = 0
    this.flushQueue()
  }
  flushQueue() {
    this.updateQueue.map(newState => this.state = newState)
    this.isBatchingUpdates = false;
  }
}

let c = new Component()
c.add()

from react.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.