GithubHelp home page GithubHelp logo

Comments (34)

soneway avatar soneway commented on May 3, 2024 69

恕我直言, 这其实就是react的bug, 整出这么大一堆概念, 就是想让你们接受这个bug, 不要踩这个坑

from daily-interview-question.

mengsixing avatar mengsixing commented on May 3, 2024 34

我理解的是:isBatchingUpdates 默认值为 false,当 react 自身的事件处理函数或 react 生命周期触发时,isBatchingUpdates 会被赋值为 true,当更新完成时又会被复原为 false。 @code-coder

from daily-interview-question.

mochen666 avatar mochen666 commented on May 3, 2024 18

@azl397985856 在React的setState函数实现中,会根据一个变量 isBatchingUpdate 来判断是直接同步更新this.state还是放到队列中异步更新 。React使用了事务的机制,React的每个生命周期和合成事件都处在一个大的事务当中。在事务的前置钩子中调用batchedUpdates方法修改isBatchingUpdates变量为true,在后置钩子中将变量置为false。原生绑定事件和setTimeout异步的函数没有进入到React的事务当中,或者当他们执行时,刚刚的事务已近结束了,后置钩子触发了,所以此时的setState会直接进入非批量更新模式,表现在我们看来成为了同步SetState。

from daily-interview-question.

xueqingxiao avatar xueqingxiao commented on May 3, 2024 10

具体可以见 ReactFiberScheduler 中的 performWork 和 performSyncWork。 楼主说的基本是对的,如果有这种 async 的 work 就不执行 batch update 如果没有 async 的就执行 batch update,setTimeout 和 promise 这些要进入 EventLoop 队列的都会被认为是 async work。

from daily-interview-question.

zhouyingkai1 avatar zhouyingkai1 commented on May 3, 2024 8

我打印的 0 0 1 1

from daily-interview-question.

wangmaoshu avatar wangmaoshu commented on May 3, 2024 6

对于 非concurrent 模式 答案 0023 屏幕显示 0 1 2 3

  1. 组件创建 屏幕显示 val 为 0

  2. componentDidMount 执行
    注意:(对于前两次之所以都是0 , 并不是 因为 isBatchingUpdates 为 true, 而是还在处理渲染状态,(只有事件系统等触发的才会isBatchingUpdates设置为true)所谓的渲染状态 也就是源码 isRendering 是 true , 这个变量是 虚拟DOM构建过程以及commit过程中都会维持 true 的状态。)
    (1)第一个setState触发,创建一个 调度任务,此时 调度链表中有 初始化创建的调度任务和这一次的调度任务。这次调度的过期时间因为是 非concurrent,所以是 sync。将 {val: 1} 对象放入到调度的updateQueue中。此时并不会马上执行,因为 第一个创建的调度还没有结束 isRendering 还是true。所以不会进入渲染阶段,屏幕还是 0.
    (2)然后打印 0.
    (3)第二个setState触发,创建一个调度任务, 此时的调度链表中有三个调度任务,分别为 初始化的时候调度、第一次setSate、第二次setSate. 这次调度的过期时间因为是 非concurrent,所以是 sync。将 {val: 1} 对象放入到调度的updateQueue中。同理 isRendering 还是 true,屏幕显示还是0.
    (4) 然后打印0.
    (5)初始化结束开始,isRendering 设置成 false。第一个初始化调度从调度链表中清除,开始进入处理剩下调度任务过程, 然后由于两次的调度任务一致, 所以,优先级一样,先处理第一次的调度任务,由于 第一次 setState 与第二次 优先级都一样, 所以在 render 阶段 收集 更新的时候都被处理了。
    (6)屏幕显示 1(并不是 两次 setState 都会输出,而是由于过期时间都一样在 render 的收集更新的时候被处理调了)

  3. setTime 执行
    (1)由于此时 isRendering 是false, 所以不会批量更新,而是第三个 setState 调用的时候,过期时间为 sync。 然后创建调度,然后直接进入更新。
    (2) 打印输出2. 屏幕显示 2
    (3)同理,第四个setState 同步执行
    (4)打印输出 3, 屏幕显示 3

对于 concurrent 模式 正常60hz刷新率情况下 答案 0011 屏幕显示 1 2

  1. 组件创建,进入调度执行,因为第一个创建的调度任务 肯定是在一帧空闲时间内执行的, 但是可能在 commit 阶段 检测到没有剩余时间了, 所以不会绘制到屏幕上。此时 isRendering 还是true

  2. componentDidMount 执行
    (1)执行第一个 setState, 此时 isRendering 是 true,仅仅是创建 调度任务 过期时间是一个异步的时间戳。并不会执行。
    (2)打印出 0
    (3)执行第二个 setState, 此时 isRedndering是true, 也是不会执行, 仅仅市创建调度任务, 注意的是,这里的异步过期时间与第一次的相同(因为 都是在一个 rendering 中)。
    (4)打印出 0
    (5)此时调度到第二个setState 的 调度任务的时候, 调度时间早已经过期了,所以退化成 同步的方式直接执行,屏幕绘制出 1。

  3. setTimeout 执行
    (1)第三个setState,进入到下一个时间循环的时候,在这之前可能执行了一次 state 变更,变成了1 所以 第三个创建调度任务的时候 val 可能是 2(只能说是可能, 因为不同的刷新率 和 组件大小都会影响了上一次的setState, 但是在目前实例仅仅有一个组件的情况下,state 肯定变成了 1 的因为必然执行了 render 过程)
    (2)打印出 1(也是可能值,只不过在 60hz 刷新率下 1 的概率最大)
    (3)第四个setState,创建调度任务,此调度任务放入队列,该过期时间可能与第三次的过期时间相同,因为异步计算时间都是在50ms 内的都作为一个过期时间。因为 两个过期时间一致, 所有中断了第三次的更新,进而更新第四次的。
    (4)打印出 1
    (5)屏幕显示 2

from daily-interview-question.

leonwens avatar leonwens commented on May 3, 2024 4

题目呢???

from daily-interview-question.

LastStranger avatar LastStranger commented on May 3, 2024 4

react-hooks时代貌似改写了这种方式,我用react-hooks写法写出来的结果都是0

from daily-interview-question.

Bjkb avatar Bjkb commented on May 3, 2024 3

@LastStranger

react-hooks时代貌似改写了这种方式,我用react-hooks写法写出来的结果都是0

更新的方式没有更改,首先是因为useEffect函数只运行了一次,其次setTimeout是个闭包,访问到的值一直是0(按照正常的写法setVal(val+1))。以例子来看的话,并没有执行更新的操作。

具体可以参考 issue讨论: facebook/react#14010

from daily-interview-question.

yusongjohn avatar yusongjohn commented on May 3, 2024 3

在16.8.6源码中调试来看,这题和批量更新是没有关系的,通常批量更新是发生在react事件机制中即触发了事件回调会设置批量更新的标志。从源码中看,该题目的解释是:当前已经处于渲染的阶段,在这个过程中,框架会主动去合并在这期间触发的更新。
react的渲染主要包含两个阶段,render阶段和commit阶段,这两个阶段的包含在performWorkOnRoot方法中,该方法设置isRendering变量,表示当前是否处于渲染阶段。
image

from daily-interview-question.

Emiya0306 avatar Emiya0306 commented on May 3, 2024 3

现在考这题已经没有意义了。。React 18默认并发就是0011,18以下就是0023。。

from daily-interview-question.

shuch avatar shuch commented on May 3, 2024 2

为什么不是 0 0 3 4

我刚开始得到结果也是0034,看楼主的解答,是前两次setState触发了批量更新,两次setState合并成了一次.

from daily-interview-question.

zhanglin-doudou avatar zhanglin-doudou commented on May 3, 2024 1

1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。

2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。

3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。

输出: 0 0 2 3

请问一下大佬,isBatchingUpdates的判断条件是什么呀?

from daily-interview-question.

SceneryCN avatar SceneryCN commented on May 3, 2024 1

当前函数式组件当中通过useState去定义state然后通过setState的回调函数的方式+1,react
输出为0,0,0,0。
是否跟当前的Concurrent mode 有关联,或者说0,0,2,3这种情况只会在生命周期当中实现并且react v<18

from daily-interview-question.

azl397985856 avatar azl397985856 commented on May 3, 2024

1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。
2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。
3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。
输出: 0 0 2 3

请问一下大佬,isBatchingUpdates的判断条件是什么呀?

#17

from daily-interview-question.

etsvain avatar etsvain commented on May 3, 2024

哦也我做对了

from daily-interview-question.

AlertTed avatar AlertTed commented on May 3, 2024

很好理解的问题 正常情况下 react是会同步更新数据的 也就是说isBatchingUpdate触发变成true 但是 如果你将state的状态值加入到setTimeout的延迟处理队列中 打印出来的值就是setState之后的值,或者你同时调用setState的第二个参数 也会做到异步操作 获取设置之后的state value

from daily-interview-question.

ggzcg avatar ggzcg commented on May 3, 2024

做成了 0022 哎。竟然有isBatchingUpdates这种东西 学习了 @yhlben 。

from daily-interview-question.

songyutou avatar songyutou commented on May 3, 2024

@LastStranger

react-hooks时代貌似改写了这种方式,我用react-hooks写法写出来的结果都是0

更新的方式没有更改,首先是因为useEffect函数只运行了一次,其次setTimeout是个闭包,访问到的值一直是0(按照正常的写法setVal(val+1))。以例子来看的话,并没有执行更新的操作。

具体可以参考 issue讨论: facebook/react#14010

应该是setVal((val) => val+1)

from daily-interview-question.

yygmind avatar yygmind commented on May 3, 2024
class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

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

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

  render() {
    return null;
  }
};

from daily-interview-question.

zhaoyinpan2 avatar zhaoyinpan2 commented on May 3, 2024

为什么不是 0 0 3 4

from daily-interview-question.

pomelo-chuan avatar pomelo-chuan commented on May 3, 2024

react的state是为view服务的,state到view是响应式的,这种代码看瞎眼

from daily-interview-question.

Code-Dog-1024 avatar Code-Dog-1024 commented on May 3, 2024

react的setState可以认为是微任务的一次执行,但是我有点不太明白,setTimeout内的setState为什么没有批量更新?

from daily-interview-question.

wangmaoshu avatar wangmaoshu commented on May 3, 2024

我打印的 0 0 1 1

因为你开启了 concurrent 模式 见我的回复 👆

from daily-interview-question.

youngs-github avatar youngs-github commented on May 3, 2024

为什么不是 0 0 3 4

虽然说是批量更新,但是两者的值指向同一个元素,因此后面的覆盖前面的

from daily-interview-question.

flftfqwxf avatar flftfqwxf commented on May 3, 2024

@wangmaoshu 那你的意思是说 如果是事件,比如点击的时候结果会不一样么?我用点击事件,做这个操作和用 didmount执行,都是一样的结果

from daily-interview-question.

miyukoarc avatar miyukoarc commented on May 3, 2024

react v17.0.0 0 0 2 3

from daily-interview-question.

Yolo-0317 avatar Yolo-0317 commented on May 3, 2024
import React from 'react';

class Exam19 extends React.Component {
  constructor(props: any) {
    super(props);
    console.log('Exam19-1');
    this.state = {
      val: 0
    };
    this.log1 = 0;
    this.log2 = 0;
    this.log3 = 0;
    this.log4 = 0;
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    this.log1 = this.state.val;
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    this.log2 = this.state.val;
    console.log(this.state.val);    // 第 2 次 log

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

      this.setState({val: this.state.val + 1});
      this.log4 = this.state.val;
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
    return <>
      <div>{this.log1}</div>
      <div>{this.log2}</div>
      <div>{this.log3}</div>
      <div>{this.log4}</div>
    </>;
  }
};

export default Exam19;

我在跑代码时发现,命令行打印是 0 0 2 3,然后我给代码加了this.logx
想在页面展示出数据,结果页面展示是0 0 2 0
image

from daily-interview-question.

sxyuanfeng avatar sxyuanfeng commented on May 3, 2024

两个变式

image

输出:1211
image

输出:1234

from daily-interview-question.

shifengdiy avatar shifengdiy commented on May 3, 2024

批处理机制:setstate不会立即更新state,而是将更新数据放入队列,在事件循环结束统一更新,提高效率

取决于setstate是否使用批处理机制,在事件监听函数和生命周期函数中异步更新,在setTimeout中同步更新;不是绝对固定,react会调整策略

from daily-interview-question.

fangxiaochao avatar fangxiaochao commented on May 3, 2024
import React from 'react';

class Exam19 extends React.Component {
  constructor(props: any) {
    super(props);
    console.log('Exam19-1');
    this.state = {
      val: 0
    };
    this.log1 = 0;
    this.log2 = 0;
    this.log3 = 0;
    this.log4 = 0;
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    this.log1 = this.state.val;
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    this.log2 = this.state.val;
    console.log(this.state.val);    // 第 2 次 log

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

      this.setState({val: this.state.val + 1});
      this.log4 = this.state.val;
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
    return <>
      <div>{this.log1}</div>
      <div>{this.log2}</div>
      <div>{this.log3}</div>
      <div>{this.log4}</div>
    </>;
  }
};

export default Exam19;

我在跑代码时发现,命令行打印是 0 0 2 3,然后我给代码加了this.logx 想在页面展示出数据,结果页面展示是0 0 2 0 image

this.setState({val: this.state.val + 1}); 执行setState方法后会更新视图,所以最新的this.log3 的值会随之更新到页面。
this.log4 = this.state.val; 这种赋值方式并不会更新视图。
若是后面再执行setState方法,页面中的log4的值会随之更新。

from daily-interview-question.

Yuweiai avatar Yuweiai commented on May 3, 2024

现在考这题已经没有意义了。。React 18默认并发就是0011,18以下就是0023。。

当前线上绝大多数还是18以下吧

from daily-interview-question.

Yangfan2016 avatar Yangfan2016 commented on May 3, 2024

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

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

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

  render() {
    return null;
  }
};

0 // react 的可控范围内 ,批量更新
0 // react 的可控范围内 ,批量更新
// 只更新了 一次 state =1
2 // react 不可控范围,同步更新 state+1 -> 2
3 // react 不可控范围,同步更新 state+1 -> 3

解释:#17

from daily-interview-question.

pipihua666 avatar pipihua666 commented on May 3, 2024

这个在react的Concurrent mode下打印的是0,0,1,1,在Sync mode下打印的是0,0,2,3

from daily-interview-question.

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.