Qianxi Vue source code-41-patch stage-trigger responsive update

1. Review & Background

The previous essay discusses in createElmdetail the logic of this method, which is based on VNodecreating real elements, which contain two scenarios:

  1. If VNodeis a custom component, call the createComponentmethod to process;
  2. If it is a normal element, by nodeOps.createElementcreating a native HTMLelement;

At the end of the previous article, I sorted out new Vuethe insertwhole process execution stack flow from to to . This process is the whole process Vuefrom new Vueto rendering to the page.

But it Vueis a responsive system. The previous process is only half completed, and the remaining half is that when the responsive data changes, Vuethe rendering of the system watcherreceives a notification to trigger re-rendering, which is what everyone often says patch 阶段.

For its Vueown internal design, as long as it is VNodethe process of rendering to the page, it is called patch, and it is not divided into two large processes, although the interior is differentiated or rendered for the first time, or the responsive data is changed and rendered. But this is not friendly to people who are beginners in Vuesource code , so I cleverly divided it into two processes and named it;

  1. The first rendering we call him 初次渲染, that is, the previous one 挂载阶段, the first time it 模板becomes DOMRendered to the page;
  2. The latter part is called patch 阶段, the latter stage is the responsive data update triggering re-rendering, your favorite dom diffis the little cutie at this stage (she abuses you thousands of times, you treat her like first love);

2. Process analysis + sample code

In order to adapt to the update of responsive data in the patchstage , we added a button to the template button#btn, the button 点击事件's handlerwill modify the data.forProp.aattribute:

  • forPatchComputedThis computed property depends on data.forProp;
  • <some-com />someKeyThe prop-bound data received by the component is alsodata.forProp

The test.html code is as follows:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Vue</title>
</head>
<body>
<div id="app">
 <button id="btn" @click="goForPatch">使 forProp.a++</button>
 <some-com :some-key="forProp"><div slot="namedSlot">SLOT-CONTENT</div></some-com>
 <div>forPatchComputed = {{forPatchComputed}}</div>
 <div class="static-div">静态节点</div>
</div>
<script src="./dist1/vue.js"></script>
<script>
  const sub = {
    template: `
      <div style="color: red;background: #5cb85c;display: inline-block">
    <slot name="namedSlot">slot-fallback-content</slot>
    {{ someKey.a + ' foo' }}
   </div>`,
    props: {
      someKey: {
        type: Object,
        default () {
          return { a: '996-rejected!!' }
       }
     }
   };
  debugger
  new Vue({
    el: '#app',
    data: {
      forProp: {
        a: 100
      }
    },
    methods: {
      goForPatch () {
         this.forProp.a++
      }
    },
    computed: {
       // 计算属性
       forPatchComputed () {
         return this.forProp.a + ' reject 996'
       }
    },
    components: {
      someCom: sub
    }
  })
</script>
</body>
</html>
复制代码

So when data.forPropis modified, there are now three watcherto trigger an update:

  1. forPatchComputedCorresponding to the computed property watcher, note that the computed property is watcheryes lazy;
  2. some-comcorresponding 渲染 watcher;
  3. div#appThe template of this root instance corresponds to 渲染 watcher;

3. Triggering responsive updates

In a reactive system, it is an obvious observer pattern, which requires us to figure out who is 观察者, who is the subject 观察, and who is responsible for the two 通信;

  • The observer is the Watcherinstance , and watcherit has been seen from the literal that it is the observer ( watchisn't the English translation 注视、看~);
  • The observed object is naturally the data itself, such as data.forPropthis object;
  • Responsible for the communication between the two is an Depinstance , Depa dependency, note that a noun is not a verb (what I want to express is 被 watcher 依赖that if 依赖别人it ); each responsive data has depit, depwhich is responsible for collecting and using this data watcher, and then the data changes Distribute a notification to make it take watcheraction ;

3.1 Review reactive implementation

There are three components to the responsiveness of data:

  1. 在初始化响应式数据的时候将 data 通过 defineReactive 方法(核心是 Object.defineProperty)将 data.forProp 变成 gettersetter,当被访问时通过 getter 被触发收集依赖,当被需改时触发 setter 通知依赖这个数据的 watcher 们更新;
  2. 初始化响应式数据时处理 computed 的逻辑:
    • 2.1 给每个计算属性创建一个 watcher,而创建 Watcher 类的实例时传入的 expOrFn 即要求值的函数,就是定义计算属性时声明的方法例如上面的例子:forPatchComputed () { return this.forProp.a + 'reject 996!!!' }
    • 2.2 计算属性是 lazy watcher,即这个计算属性被访问的时候才会求值;
    • 2.3 什么时候求值呢?被访问到时候,forPatchComputed 是根实例的模板对应的 render 函数执行的时候就会拿 forPatchComputed 对应的值,这时求值;
  3. 修改 data.forProp.a 触发 setter,而前面 getter 已经知道有三个 watcher 依赖了这个 forProp,此时通知他们三个更新;

谁来负责收集依赖 watcher 们和通知 watcher 们更新呢?Dep 类,在数据响应式初始化的时候给每个数据都创建一个 Dep 的实例,dep.denpend 收集依赖 watcherdep.notify 通知 watcher 更新。watcher 更新则是通过 watcher.update() 方法实现的;

function defineRective () {
  const dep = new Dep();
  
  Object.defineProperty(target, key {
    get () {
      if (Dep.target) dep.depend()
    }
    set () {
      dep.notify()
    }
  })
}
复制代码

下图是点击 button#btn 后因修改 this.forProp.a 而触发 setter 进入到 setter

image.png

3.2 Dep.prototype.notify

export default class Dep {

  // 把 watcher 放大响应式数据的依赖列表中
  depend () {
    if (Dep.target) {
      // Dep.target 是响应式数据 getter 时设置的,值是 watcher 实例
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()

    // 遍历 dep 中存储的 watcher,执行 watcher.update()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
复制代码

下图是点击按钮后断点调试进入到 dep.notify,你会发现 this.subs 中有三个 watcher

image.png

  1. 第一个是 forPatchComputed 这个计算属性 watcher,如图

image.png 2. 第二个是 div#app 这个根实例模板对应的渲染 watcher

image.png

  1. 第三个则是 <some-com /> 自定义组件的渲染 watcher

image.png

3.3 Wather.prototype.update

export default class Watcher {
  constructor (...) {}
 
  update () {

    if (this.lazy) {
      // 懒执行的 watcher 走这里
      // 将 dirty 置为 true,
      // 就可以让 计算属性对应的 getter 被访问到的时候
      // 再触发重新计算 computed 回调函数的执行结果
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      // 更新时一般都在这里,
      // 将 watcher 放入到 watcher 队列,
      // 然后异步更新队列
      queueWatcher(this)
    }
  }
}
复制代码

3.3.1 计算属性的 update

image.png

3.3.2 渲染 watcher 的 update

image.png

queueWatcher 是一个重点,我们为它单独开一篇;

四、总结

本篇小作文作为 patch 阶段的第一篇主要做了以下工作:

  1. 重新修改 test.html 加入了可以修改响应式数据的 button#btn 元素,以及绑定点击事件修改 data.forProp.a
  2. 重新梳理了完整的响应式流程,包含依赖收集、修改数据、派发更新的过程;并且明确了 WatcherDep以及响应式数据间的依赖和被依赖关系以及三者协作过程;
  3. 通过修改 this.forProp.a 进入到了 dep.notify(),接着看到了作为计算属性lazy watcher普通 watcherwatcher.update() 方法中的不同处理方式:
    • 3.1 lazy watcher 把 this.dirty 置为 true;这就可以使得计算属性的缓存失效,当计算属性再次被访问到的时候,就会重新求值,这个过程我们在说 Watcher 的时候详细介绍过 computed 的缓存原理;

    • 3.2 普通 watcher 包括渲染 watcher用户 watcher 都会执行 queueWatcher 方法进行异步的队列更新;

Guess you like

Origin juejin.im/post/7078897921801846798
Recommended