logo
blog
readme
Back to Blog
Back to Blog
Back to Blog

‌

‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌

© 2025 linzhe. All rights reserved.

TODO:编辑

为什么vue是组件级别的更新

tips

🚨

这篇笔记是关于 Vue 2.7 的组件流程的,如果你想了解 Vue 3 的组件流程,请参考另一篇笔记。

首先在react中,如果一个父组件更新了,默认情况下(没有做memo之类的性能优化)其包含的所有子组件都会更新,无论这些子组件是否依赖了父组件的状态, 此外,也不是从 setState 的组件开始更新,而是要从 root 开始遍历进行diff,不一定自顶向下的更新那些组件,但会进行diff。 但在vue中仅仅只会更新父组件,只会在有需要的情况下,子组件才会更新,比如依赖了父组件的状态,且父组件的状态发生了变化

1、比如这么一个组件

html
index.html
<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>

2、initProps

在initProps中会把子组件中的_props变成响应式对象,从而进行依赖收集

js
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
        )
      }
    })
  }
}

3、变更流程

  • 在vue中一个组件在挂载时,会使用watcher进行依赖收集,所以当5s后这个updateComponent就会重新执行

    js
    // 挂载
    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就是组件级别的更新

    js
    // 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
      ...
    }