Initialize the

When the _init method is executed, the initState method is called to initialize the properties in options and add the response, which includes the initialization of props

initProps

Props by initProps initialization function, defined in SRC/core/instance/state. Js

function initProps (vm: Component, propsOptions: Object) {
  // Get the props passed in by the parent component
  const propsData = vm.$options.propsData || {}
  // Mount _props to the VM and initialize it as an empty object
  const props = vm._props = {}
  // Cache each key for props
  const keys = vm.$options._propKeys = []
  // Only child component instances have the vm.$parent property
  constisRoot = ! vm.$parentif(! isRoot) {// If not the root instance, set shouldObserve in observer/index.js to false
    toggleObserving(false)}// To iterate over the props object
  for (const key in propsOptions) {
    / / the cache key
    keys.push(key)
    // Validates the props passed and returns either the props or the default value
    const value = validateProp(key, propsOptions, propsData, vm)
    if(process.env.NODE_ENV ! = ='production') {
      defineReactive(props, key, value, () = > {
        // Note here!! When the parent changes the props property passed in, it sets isUpdatingChildComponent to true, so no error is reported
        // An error will be reported when isUpdatingChildComponent is false when the child component is modifying the props property directly
        if(! isRoot && ! isUpdatingChildComponent) { warn(`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
          )
        }
      })
    } else {
      // Add a response for each key of props
      defineReactive(props, key, value)
    }
    if(! (keyin vm)) {
      // Proxy the key to the VM object
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)}Copy the code

The process of initProps is as follows:

  • Get the incomingpropdata
  • Traversing the component definitionprops
  • throughvalidatePropValidate incomingprop, to get the incomingpropData or default values
  • calldefineReactiveFunction, byObject.definePropertyWill the incomingpropData added tovm._props, and set the access descriptor.
  • If it is a root component instance, passproxyFunction, proxykeyvmOn the object. Component instanceoptions.propsthroughVue.extendWhen a child component instance is created, thepropsAll of thekeyThe agent toSub.prototype._propsOn the

Doubt point

1. IncomingpropHow is the data added tovm.$options.propsDataOn the?

In the createComponent function executes extractPropsFromVNodeData, this function is used to extract the prop of incoming data, and added to the component placeholder VNnode componentOptions. PropsData; (Described in the creating VNode article)

During the creation of the component instance, the _init function is called, in which options are merged. In the case of the component’s options merge, the incoming prop data is bound to vm.$options.propsData

2. Why is it calledtoggleObserving(false)?

If the prop data passed in is an object, defineReactive will also call Observe to add a response to the object’s property; In fact, the parent component already adds a response to all properties inside the object with observe, so there is no need to add it again

validateProp

The code is defined in SRC /core/util/props

export function validateProp (
  key: string,
  propOptions: Object,
  propsData: Object, vm? : Component) :any {
  const prop = propOptions[key]
  // True if the key attribute is not written to the component tag
  constabsent = ! hasOwn(propsData, key)// 
      
  // Get the value passed in
  let value = propsData[key]
  /* Handle Boolean type prop */
  // If prop.type is not an array, Boolean returns 0 if prop.type is the same, and -1 if not
  // If prop.type is an array, return the first identical one first. If they are the same, the corresponding index is returned; if they are different, -1 is returned
  const booleanIndex = getTypeIndex(Boolean, prop.type)
  // Props can be of type Boolean
  if (booleanIndex > -1) {
    if(absent && ! hasOwn(prop,'default')) {
      // If there is no attribute key on the component label and no default value is set, then value is false
      value = false
    } else if (value === ' ' || value === hyphenate(key)) {
      // Value is an empty string or the same as key (if key is a hump and value is a hyphen)
      // For example: 
      
        or 
       
        , 
        
      const stringIndex = getTypeIndex(String, prop.type)
      // Value is true if prop.type is Boolean
      // If prop.type is [Boolean, String], value is true
      if (stringIndex < 0 || booleanIndex < stringIndex) {
        value = true}}}if (value === undefined) {
    // If undefined is passed, get the default value
    value = getPropDefaultValue(vm, prop, key)
    const prevShouldObserve = shouldObserve
    toggleObserving(true)
    // Add a response to the default value
    observe(value)
    toggleObserving(prevShouldObserve)
  }
  if( process.env.NODE_ENV ! = ='production' &&
    !(__WEEX__ && isObject(value) && ('@binding' in value))
  ) {
    assertProp(prop, key, value, vm, absent)
  }
  return value
}
Copy the code

The validateProp function obtains the corresponding prop definition from the component based on the key. If the type attribute contains a Boolean type, it converts the prop data. For example, < Child bool>, which is set to true.

The default value is obtained if none is passed in, and a response is added to the default value for a prop that uses the default value. AssertProp is used to verify that the prop data meets expectations in the development environment.

getPropDefaultValue

GetPropDefaultValue is used to get the default value

function getPropDefaultValue (vm: ? Component, prop: PropOptions, key: string) :any {
  // no default, return undefined
  if(! hasOwn(prop,'default')) {
    return undefined
  }
  const def = prop.default
  // An error is reported if the default value is an object or array
  if(process.env.NODE_ENV ! = ='production' && isObject(def)) {
    warn()
  }
  // This is an optimization
  // If no value is passed in the first or second time, the default value is used on both occasions, and the first time the default value has been listened on
  // So the second assignment directly assigns the first listener to value
  // In this case, the response is not added again because of the __ob__ attribute
  if (vm && vm.$options.propsData &&
    vm.$options.propsData[key] === undefined&& vm._props[key] ! = =undefined
  ) {
    return vm._props[key]
  }
  // If the default value is a function and the expected type is not function, then the default value is an object or array and the function is executed to get the default value
  return typeof def === 'function'&& getType(prop.type) ! = ='Function'
    ? def.call(vm)
    : def
}
Copy the code

The initialization process is as follows

Props to update

When you modify the prop data passed by the parent component to the child component, the corresponding value of the child component also changes and triggers the child component to re-render.

Before analyzing this process, let’s look at the dependency collection process for parent and child components

Parent component dependency collection

First, the prop data is accessed during the execution of the parent render function. Add the parent component’s Render Watcher to the dep.subs of the prop data

Child component dependency collection

In the initProps function, the response is added to all prop data. When executing the child component render function, if a prop data is used, the getter method of the corresponding prop data is triggered, adding the child component render Watcher to the dep.subs of the prop data

Attribute values are basic data types

When the prop data is of a basic type, the getter method adds the child component’s Render Watcher to the dep.subs corresponding to the prop data

Property values are objects

When the prop data is an object, the parent component has added responses to the internal properties of the prop data, so let childOb =! In defineReactive Shallow && Observe (val), since the __ob__ attribute already exists, the access descriptor is not set for the attribute value, but only for vm._props. XXX.

When the child component’s Render function gets an object’s property value, the getter is triggered, essentially adding the child component’s Render Watcher to the parent component’s dep.subs for the corresponding property

Property values are the default values

When the prop data is the default value, getPropDefaultValue gets the default value and adds a response to the default value in validateProp

// ...
if (value === undefined) {
    // If undefined is passed, get the default value
    value = getPropDefaultValue(vm, prop, key)
    const prevShouldObserve = shouldObserve
    toggleObserving(true)
    // Add a response to the default value
    observe(value)
    toggleObserving(prevShouldObserve)
}
// ...
Copy the code

update

When the parent component changes prop data, the setter of the property in the parent component is triggered to notify all watchers that depend on the property to update. The parent component’s Render Watcher is not added to the dep.subs. The parent component’s Render Watcher is not added to the dep.subs. At the end of the parent component’s rerendering, the patch process is executed (the patch process is described in two separate sections, so we will learn about it here), and then the patchVnode function is executed. PatchVnode is usually a recursive process, when it encounters the component VNode, In SRC /core/vdom/patch.js:

prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // Updated props specifies the latest props of the child component
      options.listeners, // Updated Listeners Custom events
      vnode, // new parent vnode
      options.children // new children)}Copy the code

Prepatch calls updateChildComponent, passing in the latest prop data; Because the execution of the parent component render function, sub components will create a placeholder VNode, will get the latest prop in the process data, and added to the component placeholder VNode componentOptions. PropsData properties; So options.propsData in prepatch is the latest prop data

UpdateChildComponent function, it is defined in SRC/core/instance/lifecycle. In js:

export function updateChildComponent (
  vm: Component, // Subcomponent instance
  propsData: ?Object,
  listeners: ?Object,
  parentVnode: MountedComponentVNode, / / component vnode
  renderChildren: ?Array<VNode>
) {
  if(process.env.NODE_ENV ! = ='production') {
    // Set this parameter to true so that assignment to props[key] fires the set method without causing the customSetter function to give an error
    isUpdatingChildComponent = true
  }
  // ...

  / / update the props
  if (propsData && vm.$options.props) {
    toggleObserving(false)
    // Previous propsData
    const props = vm._props
    // A collection of props properties defined by the child component
    const propKeys = vm.$options._propKeys || []
    for (let i = 0; i < propKeys.length; i++) {
      const key = propKeys[i]
      const propOptions: any = vm.$options.props // wtf flow?
      // Change the props value here to trigger the component update
      props[key] = validateProp(key, propOptions, propsData, vm)
    }
    toggleObserving(true)
    // keep a copy of raw propsData
    vm.$options.propsData = propsData
  }
  
  // ...

  if(process.env.NODE_ENV ! = ='production') {
    // When the update is complete, set it to false
    isUpdatingChildComponent = false}}Copy the code

In this case, we’re just looking at props logic, so first we’re going to change isUpdatingChildComponent to true, and then the destination is going to say; Props [key] = validateProp(key, propOptions, propsData, vm) This triggers the setter of the prop data, and as long as the value of the prop is accessed while the sub-component is being rendered, it triggers the re-rendering of the sub-component according to the responsive principle. The general process is to call the update method of the child component Redner Watcher. Because the queue is executing now, the nextTick is not called again. Instead, the child component’s Render Watcher is added directly to the queue for execution (so the update process is the first parent after the child).

Back to the updateChildComponent function, next add the latest propsData to the vm.$options.propsData. And run isUpdatingChildComponent = false; The purpose of modifying this function is to prevent errors from being reported when modifying the vm._props attribute.

When setting getters and setters for prop data in the development environment, we pass customprops, which is defined in initProps. A property change triggers a setter, and if a customSetter is passed in the setter function, it executes this function. Therefore, in the development environment, if the child component directly changes the prop data, an error will be reported.

defineReactive(props, key, value, () = > {
    // Note here!! When the parent changes the props property passed in, it sets isUpdatingChildComponent to true, so no error is reported
    // An error will be reported when isUpdatingChildComponent is false when the child component is modifying the props property directly
    if(! isRoot && ! isUpdatingChildComponent) { warn(`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
      )
    }
})
Copy the code

If the prop data is an object

<hello :a="obj" />
<! -- obj = { name: 'xxx' } -->
Copy the code

If the change is internal attributes of the prop, not trigger a parent component patch process, because the parent component render function is not used in the attribute, nature also won’t trigger updateChildComponent functions; The child component’s Render Watcher has been added to the parent component’s dep.subs property during the child component’s Render process. So an update to the child component is triggered.

That is, when the parent updates an internal property, the setter method for that property is triggered, triggering the Watcher update for the child component. Because the parent component passes in an object, a reference data type, the child component gets the prop data up to date