vue和react对于组件的更新粒度有什么区别

1,657 阅读3分钟

React的重渲染概述

当调用了setState,React并不在乎有什么数据发生了改变,接着触发组件的shouldComponentUpdate,如果返回true则调用render,然后以同样的办法依次更新所有子组件,如果返回false则阻止render方法调用及子组件更新。换句话说,更新视图的控制权由shouldComponentUpdate掌握,而默认情况下该方法返回true

React的更新粒度

自顶向下的进行递归更新,不论组件的层级多深,所有的组件都会递归(递归概念请见调用栈帧)的重新render(再不进行手动的优化情况下),因此创建了fiber

他们能用收集依赖的这套体系吗?不能,因为他们遵从Immutable的设计思想,永远不在原对象上修改属性,那么基于 Object.definePropertyProxy 的响应式依赖收集机制就无从下手了(你永远返回一个新的对象,我哪知道你修改了旧对象的哪部分?)

同时,由于没有响应式的收集依赖,React 只能递归的把所有子组件都重新 render一遍,然后再通过 diff算法 决定要更新哪部分的视图,这个递归的过程叫做 reconciler,听起来很酷,但是性能很灾难。

Vue的重渲染概述

订阅式机制决定了它不仅知道哪些数据发生了更新,也知道这个数据更新了之后当前组件以及子组件的视图需不需要重新渲染。这是通过“依赖收集”实现的,Vue的视图template会编译成render函数,在数据(data/props/compu ted)中定义了getter,每次调用各个组件的render函数时,通过getter,就能知道哪些数据被哪些组件的视图所依赖,下一次对这些数据赋值时,也就是调用setter,相应的视图就能触发重渲染,而无关的组件则不需要再次调用render函数,节省了开销

Vue的更新粒度(vue的精准更新是如何做到的呢)

每一组件都有自己的render watcher,它控制着当前组件的视图更新,但是并不会掌管ChildComponent的更新,

当组件更新到子组件的时候,会走patchVnode方法。

如果是普通的节点,就直接diff更新,patchVnode结束,如果要是子组件(<child/>)那么在diff的过程只会对子组件的props,listenters等属性更新,!!而不会深入到组件内部进行更新。(重点!)

那么props的更新是如何触发重渲染呢?

props传递给子组件的时候,保存到了子组件的_props上,子组件组件初始化阶段,会对props响应式处理,子组件对props的访问,props就拥有了子组件的render watcher(副作用函数收集到了自己的dep中)。父组件重新render的时候,重新计算子组件的props,触发了props的setting,所以子组件就重新render了。

// 就是這句話,觸發了對於 _props.msg 的依賴更新。
props[key] = validateProp(key, propOptions, propsData, vm)

补充: vm.$forceUpdate:迫使Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。,vm.$forceUpdate本质上就是触发了渲染watcher的重新执行,和你去修改一个响应式的属性触发更新的原理是一模一样的,它只是帮你调用了vm._watcher.update()(提供便捷的api,在设计模式中叫做门面模式

父子组件的更新会经历两个 nextTick 吗?

答案是不会: 注意看源码 queueWatcher 里的逻辑,父组件更新的时候全局变量 isFlushing 是true,所以不会等到下个tick 执行,而是直接推进队列里,在一个tick 里一起更新掉了。

父组件更新的 nextTick 中会执行这个,会去循环运行 queue 里的watcher

function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
for (index = 0; index < queue.length; index++) {
// 更新父組件
watcher.run()
}
}

而在父组件更新的过程中又触发了子组件的响应式更新,导致触发了 queueWatcher 的话,由于 isFlushing 是true,会这样走 else 中的逻辑,由于子组件的 id 是大于父组件的 id 的,所以会在插入在父组件的 watcher 之后,父组件的更新函数执行完毕后,自然就会执行子组件的 watcher 了。这是在同一个tick 中的。

if (!flushing) {
    queue.push(watcher)
} else {
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) i--
    queue.splice(i + 1, 0, watcher)
}

只是在队列中加入了这个 watcher 直接执行。