Vue.js allows a component user to pass props parameters outside the component, and the component gets the values of these props to implement various functions. In this article, we will discuss the initialization and update process of the component props.

In the previous article, we knew that the first argument to the setup function was props. In this article, we’ll take a look at how props is initialized and updated.

Before we begin, let’s make two concepts clear:

Props configuration: Is the Props property written when writing a component. It describes the Props data type and default value of a component. For example, when defining a component :props: [‘ MSG ‘]

Props data: Is the data passed to a component when it is being used.

PropsInitialization process

normalizePropsOptionsforPropsStandardized configuration

The first step to mount a component is to call createComponentInstance to create the component instance object. During initialization, the normalizePropsOptions method is implemented to configure the standardized props:

Const instance: ComponentInternalInstance = {/ / omit... PropsOptions: normalizePropsOptions(type, appContext), // omit... return instance }Copy the code

Let’s take a look at the actual logic for the normalizePropsOptions method configured for the standardized Props.

export function normalizePropsOptions( comp: ConcreteComponent, appContext: AppContext, asMixin = false ): NormalizedPropsOptions { // 1. const cache = appContext.propsCache const cached = cache.get(comp) if (cached) { return cached } const raw = comp.props const normalized: NormalizedPropsOptions[0] = {} const needCastKeys: NormalizedPropsOptions[1] = [] // 2. let hasExtends = false if (__FEATURE_OPTIONS_API__ && ! isFunction(comp)) { const extendProps = (raw: ComponentOptions) => { if (__COMPAT__ && isFunction(raw)) { raw = raw.options } hasExtends = true const [props, keys] = normalizePropsOptions(raw, appContext, true) extend(normalized, props) if (keys) needCastKeys.push(... keys) } if (! asMixin && appContext.mixins.length) { appContext.mixins.forEach(extendProps) } if (comp.extends) { extendProps(comp.extends) } if (comp.mixins) { comp.mixins.forEach(extendProps) } } if (! raw && ! hasExtends) { cache.set(comp, EMPTY_ARR as any) return EMPTY_ARR as any } // 3 if (isArray(raw)) { for (let i = 0; i < raw.length; i++) { const normalizedKey = camelize(raw[i]) if (validatePropName(normalizedKey)) { normalized[normalizedKey] = EMPTY_OBJ } } } else if (raw) { // 4 for (const key in raw) { const normalizedKey = camelize(key) if (validatePropName(normalizedKey)) { const opt = raw[key] const prop: NormalizedProp = (normalized[normalizedKey] = isArray(opt) || isFunction(opt) ? { type: opt } : opt) if (prop) { const booleanIndex = getTypeIndex(Boolean, prop.type) const stringIndex = getTypeIndex(String, prop.type) prop[BooleanFlags.shouldCast] = booleanIndex > -1 prop[BooleanFlags.shouldCastTrue] = stringIndex < 0 || booleanIndex < stringIndex // if the prop needs boolean casting or default value if (booleanIndex > -1 || hasOwn(prop, 'default')) { needCastKeys.push(normalizedKey) } } } } } // 5 const res: NormalizedPropsOptions = [normalized, needCastKeys] cache.set(comp, res) return res }Copy the code

To standardize the Props configuration

  1. From the firstappContext.propsCacheTo get the component object askeyConfiguration cache, if the cache result is directly returned;
  2. To deal withextendsandmixinsIn thePropsAttribute, both of which extend the definition of the component, so you need to recurse what they definePropsperformnormalizePropsOptionsMethod, and then put the result in the component’s stored result. We know from the handlingextendsThere can only be one and it gets prioritized,mixinsThere can be more than one)
  3. If the Props configuration is an array and each element is a string, change the string to a camel name, and I create an empty object for each key.
Definition: ['age','message-id'] Props: {age: {}, messageId: {}}Copy the code
  1. ifProps configurationIs an object, then normalize each that does not begin with a $propThe definition. First we convert arrays and functions into objects:{type: prop}. And then determine ifpropthetypeProperties are definedBoolean, is marked as data to be converted; ifpropthetypeProperties of theBooleanThere is,StringDoes not exist orBooleaninStringPreviously, this is marked as needing to be converted to Boolean.
Definition: {name: String, intro: [Boolean, String]}Copy the code

  1. ifpropcontainsdefaultOr the type containsBooleanIs marked askeyThe value of theneedCastKeysIn the.

  1. I’m going to getpropOptionsThe cache toappContext.propsCacheIn the.

initPropsSet up thePropsInitialize the

SetupComponent was initialized to initialize initProps(instance, props, isStateful, isSSR) when setupComponent was setup as a component object. Let’s take a look at it:

The code logic for initProps is as follows:

The initialization of initProps is divided into four steps:

  1. Set the Props;
  2. There is no transmissionPropsSet its value toundefined;
  3. If the software is in the development environment, verify Props and display an error message.
  4. willpropsBecomes responsive data assigned to the component instance objectattrsAssign to the component instance object.

So let’s do this step by step.

setFullPropsSet up thepropsThe value of the

To facilitate understanding, here is an example:

Setting process explanation:

  1. traverserawPropsIs the value of, in our example{name: "Lan", address: Beijing dongcheng, age: "18", intro: ""};
  2. ifpropsOptionsThere arerawPropsThe correspondingkeyIf no conversion is required, the value is directly assigned topropsIf the value needs to be converted, it is temporarily saved torawCastValues; ifpropsOptionsThere is norawPropsThe correspondingkey, and is not an event-related attributeattrsIn the.
Props = {address: "props "}; attrs = {age: 18}; rawCastValues = {intro: "", name: "Lan"};Copy the code
  1. throughresolvePropValueTo convert:
  • If you havedefaultUse the default value if the parent component does not pass a value, otherwise use the value passed in (as in the example)name)
  • ifkey[0] === true, if the parent component does not pass a value and does not have a default valuefalse, if the parent component passes a value, in which case ifkey[1] === trueAnd the string is empty. This is set totrue(In the exampleintro), otherwise,false.
Props = {address: "props", intro: true, name: "Lan"}; attrs = {age: 18};Copy the code
validatePropsvalidationpropsValue validity

The validity verification mainly has the following rules:

  1. If a value must be passed and no value is passed, a warning is reported
  2. If no value must be passed, null is passed and the value is returned
  3. Warning if the types do not match
  4. A warning is reported if the validator fails to validate an passed value
propsSet to shallow responsive object and assignment
instance.props = shallowReactive(props)
instance.props = props
instance.attrs = attrs
Copy the code

This step is easy to understand. ShallowReactive: Only changes in props are monitored. Changes in internal properties are not monitored.

One quick question: Why are props set to responsive?

At this point, the initialization of prop is complete. Later in the setup function, you can pass the props object to the child component.

PropsUpdate process

updatePropsTrigger the rendering process

Props is defined in the parent component and passed to the child component, so changes in the Props value are rerendered by the parent component. The rerendering of the parent component triggers the patch, and then the updateComponent process is triggered when the child component is updated. HasPropsChanged returns true because the props is changed, so shouldUpdateComponent is true. At this point, set the next of the child component to the new VNode, and then perform the re-rendering of the child component.

const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => { const instance = (n2.component = n1.component)! if (shouldUpdateComponent(n1, n2, optimized)) { // normal update instance.next = n2 // in case the child component is also queued, remove it to avoid // double updating the same child component in the same flush. invalidateJob(instance.update) // instance.update is the reactive effect. instance.update() } } export function shouldUpdateComponent( prevVNode: VNode, nextVNode: VNode, optimized? : boolean ): boolean { const { props: prevProps, children: prevChildren, component } = prevVNode const { props: NextProps, children: nextChildren, patchFlag} = nextVNode return hasPropsChanged(prevProps, nextProps, emits) }Copy the code

The componentUpdateFn method is executed when the child component is re-rendered, and the new VNode has a value for Next. The triggered updateComponentPreRender process calls the updateProps method to update the props of the child component instance object.

const updateComponentPreRender = ( instance: ComponentInternalInstance, nextVNode: VNode, optimized: boolean ) => { nextVNode.component = instance const prevProps = instance.vnode.props instance.vnode = nextVNode Instance. next = null // updateProps(instance, nextvNode. props, prevProps, Optimized)}Copy the code

Before updating the props, perform the patch operation on the subTree VNode to update subcomponents.

Here we go back to see how the Pros have been updated.

updatePropsDetails of the update

Its main goal is to update the props of the child component’s instance object with the new values obtained when the parent component renders.

The component’s VNode PatchFlags can be known during the compilation phase of the Vue:

  • If it is PatchFlagsisPatchFlags.PROPS, only updatesVnode. DynamicProps ` namelyDynamic propsThe value of the.
  • If the current componentPatchFlagsisPatchFlags.FULL_PROPS“Is executed firstsetFullPropsProcess, and then delete the ones that are no longer usedDynamic propsThe value of the.

Here is the code that can be skipped:

export function updateProps( instance: ComponentInternalInstance, rawProps: Data | null, rawPrevProps: Data | null, optimized: boolean ) { const { props, attrs, vnode: { patchFlag } } = instance const rawCurrentProps = toRaw(props) const [options] = instance.propsOptions let hasAttrsChanged = false if ( // always force full diff in dev // - #1942 if hmr is enabled with sfc component // - vite#872 non-sfc component used by sfc component ! ( __DEV__ && (instance.type.__hmrId || (instance.parent && instance.parent.type.__hmrId)) ) && (optimized || patchFlag > 0) &&! (patchFlag & PatchFlags.FULL_PROPS) ) { if (patchFlag & PatchFlags.PROPS) { // Compiler-generated props & no keys change, just set the updated // the props. const propsToUpdate = instance.vnode.dynamicProps! for (let i = 0; i < propsToUpdate.length; i++) { let key = propsToUpdate[i] // PROPS flag guarantees rawProps to be non-null const value = rawProps! [key] if (options) { // attr / props separation was done on init and will be consistent // in this code path, so just check if attrs have it. if (hasOwn(attrs, key)) { if (value ! == attrs[key]) { attrs[key] = value hasAttrsChanged = true } } else { const camelizedKey = camelize(key) props[camelizedKey] = resolvePropValue( options, rawCurrentProps, camelizedKey, value, instance, false /* isAbsent */ ) } } else { if (__COMPAT__) { if (isOn(key) && key.endsWith('Native')) { key = key.slice(0, -6) // remove Native postfix } else if (shouldSkipAttr(key, instance)) { continue } } if (value ! == attrs[key]) { attrs[key] = value hasAttrsChanged = true } } } } } else { // full props update. if (setFullProps(instance, rawProps, props, attrs)) { hasAttrsChanged = true } // in case of dynamic props, check if we need to delete keys from // the props object let kebabKey: string for (const key in rawCurrentProps) { if ( ! rawProps || // for camelCase (! hasOwn(rawProps, key) && // it's possible the original props was passed in as kebab-case // and converted to camelCase (#955) ((kebabKey = hyphenate(key)) === key || ! hasOwn(rawProps, kebabKey))) ) { if (options) { if ( rawPrevProps && // for camelCase (rawPrevProps[key] ! == undefined || // for kebab-case rawPrevProps[kebabKey!] ! == undefined) ) { props[key] = resolvePropValue( options, rawCurrentProps, key, undefined, instance, true /* isAbsent */ ) } } else { delete props[key] } } } if (attrs ! == rawCurrentProps) { for (const key in attrs) { if (! rawProps || ! hasOwn(rawProps, key)) { delete attrs[key] hasAttrsChanged = true } } } } }Copy the code

Ask a question

When passing dynamic data, we prefix attributes with a colon :, such as :address=”address”. What does this colon: do? Why don’t we have a colon: when we’re configuring the functions to handle address?

<HelloWorld name="Lan" :address="address" age="18" intro="" />