Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何理解Vue的批处理和react有什么区别? #75

Open
wuyunqiang opened this issue Sep 4, 2024 · 0 comments
Open

如何理解Vue的批处理和react有什么区别? #75

wuyunqiang opened this issue Sep 4, 2024 · 0 comments

Comments

@wuyunqiang
Copy link
Owner

先有问题再有答案

  1. 如何理解Vue的批处理?
  2. 与浏览器的event loop有什么关系 ?
  3. vue自动收集 批量更新的基本单位是什么?
  4. 和react批处理有什么区别嘛?

前置文章

react中的内循环与批处理

面试官:vue用三年多了?来说说你是如何理解vue的...

面试官:react用三年多了? 来看看下面的知识点你是如何理解的...

浏览器:帧&事件循环

浏览器:帧&渲染流程

js三座大山之异步六实现微任务的N种方式

如何理解vue的批处理

case1

<script setup>
const count = ref(0);
let render = 0;

onUpdated(() => {
  console.log("update render:", render++);
});

const onBtn1 = () => {
  for (let i = 0; i < 5; i++) {
    count.value++;
  }
};
</script>

在这段代码中,for循环五次增加count的值,但是并不会触发五次组件的重新渲染。在Vue.js的响应式系统中,如果在同一个事件循环(event loop)内多次改变一个响应式变量的值,那么Vue.js会在这个事件循环结束后,用一个微任务(microtask)去触发这个响应式变量的依赖更新,然后进行组件的重新渲染。

因此,尽管count.value在onBtn1函数中改变了五次,但是它们都在同一个事件循环内,所以Vue.js会在这个事件循环结束后,只触发一次更新,并且只会重新渲染一次组件。所以,console.log("update render:", render++);只会打印一次最终值。

btn1.png

case2

<script setup>
const count = ref(0);
let render = 0;

onUpdated(() => {
  console.log("update render:", render++);
});

const onBtn2 = () => {
  for (let i = 0; i < 5; i++) {
    setTimeout(() => {
      count.value++;
    }, 1000);
  }
};
</script>

在 onBtn2 函数中,count.value 的增加操作被放在了一个 setTimeout 函数里,设置了 1000ms 的延迟。同时,这个操作执行了 5 次。

由于 setTimeout 函数会让count.value++ 分别在5个不同的宏任务(macrotask)队列中执行,事件循环会处理完JavaScript主线程上的任务,然后再处理宏任务队列中的下一个任务。在这里,这将分别在大约1秒的时间间隔内递增5次 count.value。

因此,这段代码里的 console.log("update render:", render++); 将会被打印五次,因为每次 setTimeout 中的回调函数被执行时,count.value 的改变,会触发组件的重新渲染,从而触发 onUpdated。而多次count.value++操作都处于不同的宏任务队列中,所以组件将会有 5 次独立的更新和重新渲染操作。

btn2.png

case 3

const count = ref(0);
let render = 0;

onUpdated(() => {
  console.log("update render:", render++);
});

const onBtn3 = () => {
  setTimeout(() => {
    for (let i = 0; i < 5; i++) {
      count.value++;
    }
  }, 1000);
};

在 onBtn3 函数中,count.value 的增加操作被放在了一个 setTimeout 函数里,设置了 1000ms的延迟,并在这个 setTimeout 函数内通过 for 循环进行了五次递增操作。

setTimeout 函数会将回调函数放入 Event Loop 的宏任务队列,会等到 JavaScript 主线程上的代码执行完毕后,再去执行这个队列中的任务。

在一个单独的 setTimeout 任务中,尽管 count.value 被改变了五次,但这五次改变都发生在同一个宏任务队列中。根据 Vue 的异步更新策略,这些改变将被合并,只会触发一次组件的更新。因此,这段代码里的 console.log("update render:", render++); 将会只被打印一次。

btn3.png

vue & event-loop总结

截屏2024-07-23 下午3.06.31.png

Vue 会在同一个事件循环中收集和合并像 count.value++ 这样的触发视图更新的操作。然后,在当前事件循环结束后,使用微任务microtask)一次性地更新视图,同时确保视图使用的是最新的数据。

react中的批处理

截屏2024-05-16 下午7.21.21.png

关于react批处理详情可以参考这篇文章
react中的内循环与批处理

在 React 的同步生命周期方法或事件处理器中,多次连续的状态更新通常会被合并,所以只会引起一次重新渲染。这种行为称为状态更新的批处理(batching)。批处理提高了性能,因为它减少了不必要的重新渲染次数。

在某些情况下,这种批处理机制可能不会按预期工作,导致状态更新被单独处理,从而引起多次渲染。以下是一些批处理可能“失效”或不被应用的情况:
异步操作:只有同步代码中的状态更新会自动被批处理。在异步操作中(如 setTimeout、Promise、异步事件处理等)触发的状态更新不会被自动批处理,每个状态更新都可能引起一次单独的重新渲染。
非 React 事件处理器:由非 React 的事件管理(如直接添加到 DOM 元素上的事件监听器)触发的状态更新,不会被自动批处理,因为 React 无法捕获和控制这些更新。

react&vue 批处理差异

主要的差异在于两者的合并契机不同 vue是以event-loop为单位的自动合并。react是以内部的生命周期方法或事件处理器为单元的手动合并。

const count = ref(0);
let render = 0;

onUpdated(() => {
  console.log("update render:", render++);
});

const onBtn3 = () => {
  setTimeout(() => {
    for (let i = 0; i < 5; i++) {
      count.value++;
    }
  }, 1000);
};

这段代码vue只会更新一次。

如果换为react的版本(v16)

    const [count, setCount] = useState(-1);

    useEffect(() => {
        console.log('test update', count);
    }, [count]);

    const onBtn3 = () => {
        setTimeout(() => {
            for (let i = 0; i < 5; i++) {
                setCount(i);
            }
        }, 1000);
    };

截屏2024-07-23 下午3.44.15.png

setTimeout虽然是在同一个时间循环内,但是因为不在react生命周期和合成事件中 所以每次调用setCount都会触发更新。

当然这个批处理的合并机制 在react 18中已经有所变更. 在react生命周期外也可以做到任务合并。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant