🚨
这篇笔记是关于 Vue 2.7 的组件流程的,如果你想了解 Vue 3 的组件流程,请参考另一篇笔记。
首先在react中,如果一个父组件更新了,默认情况下(没有做memo之类的性能优化)其包含的所有子组件都会更新,无论这些子组件是否依赖了父组件的状态,
此外,也不是从 setState 的组件开始更新,而是要从 root
开始遍历进行diff,不一定自顶向下的更新那些组件,但会进行diff。
但在vue中仅仅只会更新父组件,只会在有需要的情况下,子组件才会更新,比如依赖了父组件的状态,且父组件的状态发生了变化
<div id="app">
<test :msg="message.text"></test>
<linzhe></linzhe>
</div>
<script type="module">
import Vue, { reactive, h, ref } from './vue.esm.js'
const vm = new Vue({
components: {
test: {
props: ['msg'],
updated() {
// 会触发
console.log('test update')
},
template: `<div>{{msg}}</div>`,
},
linzhe: {
template: `<div>linzhe141</div>`,
updated() {
// 不会触发
console.log('linzhe update')
},
},
},
data() {
return {
message: { text: 'Hello World!' },
}
},
updated() {
// 会触发
console.log('app update')
},
})
vm.$mount('#app')
window.vm = vm
setTimeout(() => {
vm.message.text = 'ssssssssssss'
console.log('update')
}, 2000)
</script>
在initProps
中会把子组件中的_props
变成响应式对象,从而进行依赖收集
function initProps$1(vm, propsOptions) {
...
// propsData => :msg="message" => {msg: {text:Hello World!}},这个msg对象是父组件传递过来的响应式数据
const propsData = vm.$options.propsData || {}
const props = (vm._props = shallowReactive({}))
const keys = (vm.$options._propKeys = [])
const isRoot = !vm.$parent
for (const key in propsOptions) {
keys.push(key)
// 这个value就是用户传递的props.msg,
// 举例:如果是一个响应式数据,那么也还是一个响应式数据
const value = validateProp(key, propsOptions, propsData, vm)
// 将符合的props的key定义成响应式,所以子组件就会对这些props进行依赖收集
// 当父组件进行updateChildComponent修改了props[key],对应的子组件才会进行更新,否则不会更新
//! 这行代码是重点,并且这个流程和vue3不一样
defineReactive(props, key, value, () => {
// 防止用户在子组件中手动修改props的值,因为要保证单向数据流
if (!isRoot && !isUpdatingChildComponent) {
warn$2(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
}
}
在vue中一个组件在挂载时,会使用watcher进行依赖收集,所以当5s后这个updateComponent
就会重新执行
// 挂载
function mountComponent(vm, el, hydrating) {
...
updateComponent = () => {
// DIFF
vm._update(vm._render(), hydrating)
}
...
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true /* isRenderWatcher */
)
}
在进行DIFF时,当vnode是组件,就会进行updateChildComponent
,在updateChildComponent中就会更新最新传递的props,而不会深入到组件内部进行更新。
由于initProps
函数,所以子组件的_props
变成了响应式数据了,并且响应式数据又变更了,那么自然会重新进行子组件的updateComponent
,
从而进行重新渲染了,而像那种没有依赖父组件状态的linzhe
组件,因为_props
没有变化,就不会重新渲染了,所以综上来说vue就是组件级别的更新
// 1 DIFF
function patchVnode( oldVnode, vnode, ...) {
...
// 组件的vnode
// componentVNodeHooks
// { data: {hook: {..., prepatch, ...}} }
let i;
const data = vnode.data;
if (isDef(data) && isDef((i = data.hook)) && isDef((i = i.prepatch))) {
i(oldVnode, vnode) // prepatch(oldVnode, vnode)
}
}
prepatch(oldVnode, vnode) {
...
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
}
// 2 更新子组件
function updateChildComponent(...){
...
isUpdatingChildComponent = true //只有在更新子组件时,才能去改props,否则都会报错,保证单选数据流
// update props
// vm 这里的vm是子组件实例
// 这里就是解释了为什么vue是组件级别的更新了
// 就比如linzhe这个组件,由于它没有对应的props,
// 那么propsData是undefined,所以props这个响应式数据就不会更新,
// 对应的渲染watcher也不会重新执行,所以子组件就不会更新
if (propsData && vm.$options.props) {
const props = vm._props;
const propKeys = vm.$options._propKeys || [];
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i];
const propOptions = vm.$options.props; // wtf flow?
props[key] = validateProp(key, propOptions, propsData, vm);
// 这里就会触发对应的子组件重新更新了,及其依赖props副作用函数也会更新,比如computed
}
}
isUpdatingChildComponent = false
...
}