IniState is a step of initialization between initInject and initProvide in vm._init that handles initialization of properties like props, methods, data, computed, and watch. IniState starts with an initialization of vm._watchers = []. Vm. _watchers is used to store all watcher instances generated during the current vm._render process. Because all the attributes except methods involve reactive data, watcher is involved when it comes to reactive data.

For data, we know that when a component calls its _render method, its Render Watcher subscribs to data dependent data in the view, and when those data change, the component calls _Render again to generate a new view. How do the other three trigger the VM’s _render? Or how did you get into trouble with Watcher?

initProps

The props data passed in from the parent VM is called propsData and is stored in the component’s own $options.propsData. The props property of the component options is called propsOptions, and the data is stored in $options.props of the component itself. The propsData process for initProps can be summarized as follows:

  1. Initialize the VM_propsProperty is an empty object,vm.$options._propKeys = []
  2. In turn, traversepropsOptionsProperty name, checksumpropsDataThe value of the corresponding attribute, adds all the values that pass the verification to the attribute namevm._propsAnd push the property name tovm.$options._propKeys, then converts it to reactive, and finally proxies this property to the VM.

It is important to note that for non-root components, converting properties in vM. _props to reactive is “shallow”, meaning that values of object or array types in vM. _props are not observed further.

_props is responsive, which means that a change in the _props will trigger an update to the subscribed watcher, assuming that the watcher is the render watcher of the current VM. This.Aprop = ‘xx’ triggers vm._render, but this gets a warning against doing so: when the parent updates, the props that were manually modified in the child are overwritten by the props that were passed by the parent. How does a legitimate change to the data in props trigger a component update? “Becomes” How does a parent update trigger a child update? Does it have to trigger a child update?” The problem. It is easy to locate the code associated with this:

// When the child component calls the updateChildren function in the parent _render process, it is handled by the patchVnode function
function patchVnode(oldVnode, vnode,) {
  // ...
  let i
  let data = vnode.data

  if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
    i(oldVnode, vnode);
  }
  // ...
}

const componentVNodeHooks = {
  // ...

  prepatch: function prepatch (oldVnode, vnode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance

    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children)},// ...
}

function updateChildComponent(vm, propsData, listeners, parentVnode, renderChildren) {
  // ...

  // update props
  if (propsData && vm.$options.props) {
    toggleObserving(false)
    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;

      props[key] = validateProp(key, propOptions, propsData, vm)
    }

    toggleObserving(true)
    vm.$options.propsData = propsData
  }

  // ...
}
Copy the code

Component VNode’s prepatch hook will be called. This hook will update the propsData. If the propsData does change, Props [key] = validateProp(Key, propOptions, propsData, VM) triggers the Render Watcher update of the child component to refresh the view. In addition, changes to the $listener and $attrs attributes also do this. So you also know that updates to the parent component don’t necessarily trigger updates to the child component unless propData, $Listener, and $attrs change.

initComputed

InitComputed generates a watcher instance for all data in computed. Unlike Render Watcher, watcher instances generated for computed attributes have {lazy: The true} option, which is exclusive to computed watcher, is called lazy Watcher for convenience. Lazy watcher is not immediately evaluated when instantiated. It is evaluated only when watcher.dirty is true, while its watcher.update method sets watcher.dirty to true and then sets it back to false. Suppose the following component has the following:

{
  data() {
    return {
      hi: 'hello'}},computed: {
    helloKitty() {
      return this.hi + ', kitty } }, render() { return 
      
{this.helloKitty} }, mounted() { setTimeout(() => this.hi = '
hi', 2000)}}Copy the code

How is component creation, Mounted, and updated driven by Watcher?

First, initData changes $data.hi to responsive, and then, when initComputed, creates a lazy watcher for helloKitty. Because it is lazy, the getter for $data.hi is not triggered yet. The last vm._render call calls helloKitty, which results in lazy Watcher evaluation:

// This function is triggered when accessing computed, and key is the key of the computed property
function computedGetter() {
  const watcher = this._computedWatchers && this._computedWatchers[key]

  if(watcher) {
    if(watcher.dirty) {
      / / evaluated
      watcher.value = watcher.evaluate()
    }

    if(Dep.target) {
      watcher.depend()
    }

    return watcher.value
  }
}
Copy the code

Watcher. Evaluate updates dep. target to lazy Watcher, and then triggers its getter to access $data.hi, and then restores it to its original value. The value before the update is the render Watcher of the current component, since this evaluation takes place during the render Watcher evaluation. $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi = $data.hi That is, the responsive data $data.hi that relies on computed data ends up being subscribed to both the lazy Watcher and the component’s Render Watcher.

So, when $data.hi changes, the triggering of its setter causes both the lazy watcher and the Render Watcher to update. First, the dirty property of the lazy Watcher update is set to true, and then the _update of the component is called during render Watcher run (which also results in _render). HelloKitty is accessed again, which triggers the computedGetter above, and finally updates and returns the updated value, completing the view update. In addition, according to the computedGetter, the lazy Watcher will reevaluate only when watcher. Dirty is true. That is, as long as the dependent reactive data has not changed, the lazy Watcher will directly return the value obtained last time. There is no re-evaluate, which is what the official documentation says: calculated attributes are cached based on their reactive dependencies.

initWatch

Create a separate watcher for each data in options.watch. This watcher differs from render watcher and lazy watcher in that The second argument that is finally passed to the new Watcher(VM, key, handler) is a string (the path that the current VM can access to the corresponding responsive data), and the third argument is a callback to the corresponding responsive data when it changes. The other two watcher are created with the second argument being a function (one updateComponent and one computedGetter) and the third argument being a noOP function that does nothing. This means that the other two Watchers do not need a third parameter to do what they want to do. (The evaluation process includes adding, updating dependencies, and updating components, which should be a callback.) In addition to the side effects of adding and updating dependencies, side effects such as updating components are not included, so the corresponding callback requires an extra parameter. Think of it this way: The Render Watcher and lazy Watcher created for computed seem to be customized for component updates. The normal watcher created for $options.watch is supposed to be the correct way to use watcher: it listens for a value, and when the value changes, the corresponding callback is triggered.

The usual watcher dependencies are added in the function returned by parsePath.