The article was first published on personal blog Small gray space
Normalization options
When new Vue creates a Vue instance for initialization, the first operation is the merge option. In the merge option, there are different merge strategies for different scenarios
if (options && options._isComponent) { // When initializing subcomponents, merge the options of subcomponents initInternalComponent(vm, options) } else { // Merge options when calling new Vue externally vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) }
You can see that when you create a Vue instance, you call mergeOptions to merge options. In the process of merging options, the first thing to do is to normalize options
/** * Normalize the projects input directives respectively */ normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child)
Let's take a look at the specific implementation of the above three functions
Normalized props
According to the instructions in the Vue usage document, there are two formats for defining props: array and object. Let's take a look at the use of these two formats first
// Lists props as an array props: ['name', 'age'] // Lists props as objects. Lists props as objects. You can specify the data type of each prop props: { name: string, age: age, address: object }
Since props has two definition formats, it will be inconvenient to use it. Therefore, it is necessary to standardize props and unify it into object format. Now let's look at the implementation of normalizeProps
function normalizeProps (options: Object, vm: ?Component) { const props = options.props if (!props) return const res = {} let i, val, name if (Array.isArray(props)) { // List props in array format i = props.length while (i--) { val = props[i] if (typeof val === 'string') { // Each item in the array must be a string. Each item is stored in an object. The key of each item refers to the most object, and the corresponding value is {type: null} name = camelize(val) res[name] = { type: null } } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.') } } } else if (isPlainObject(props)) { // If props is listed in object format, isplanobject determines whether a value is an ordinary object for (const key in props) { val = props[key] // Convert the key of the object into small hump format name = camelize(key) // Evaluate using ternary expressions res[name] = isPlainObject(val) ? val : { type: val } } } else if (process.env.NODE_ENV !== 'production') { warn( `Invalid value for option "props": expected an Array or an Object, ` + `but got ${toRawType(props)}.`, vm ) } options.props = res }
After the analysis of the above code, we can finally summarize the results of props standardization for different writing methods
For array format
props: ['name', 'age']
The final conversion is as follows
props: { name: { type: null }, age: { type: null } }
For object formats
props: { name: string, age: number, address: { type: object } }
The final conversion is as follows
props: { name: { type: string }, age: { type: number }, address: { type: object } }
Normalized inject
Inject cannot be used alone in Vue, and it needs to be used together with provide. Let's take a look at Vue's explanation of provide/inject
To allow an ancestor component to inject a dependency into all its descendants, no matter how deep the component level is, and it will always take effect when its upstream and downstream relationships are established
The inject option should be:
- An array of strings, or
An object whose key is the local binding name and whose value is:
- Search for the key (string or Symbol) used in the available injection content, or
An object whose:
- from property is the key (string or Symbol) used for searching in the available injected content
- default property is the value used in case of degradation
Now let's look at the implementation of normalizeInject. The inject option can be written in two ways: array and object. As with props, it will eventually be converted to object format
function normalizeInject (options: Object, vm: ?Component) { const inject = options.inject if (!inject) return const normalized = options.inject = {} // Array format if (Array.isArray(inject)) { for (let i = 0; i < inject.length; i++) { // from: attribute is the key (string or Symbol) used for searching in the available injected content normalized[inject[i]] = { from: inject[i] } } } else if (isPlainObject(inject)) { // Object format for (const key in inject) { const val = inject[key] normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val } } } else if (process.env.NODE_ENV !== 'production') { warn( `Invalid value for option "inject": expected an Array or an Object, ` + `but got ${toRawType(inject)}.`, vm ) } }
Normalized directive
Let's first look at the usage of instruction options. Vue allows us to customize instructions, and it provides five hook functions bind, inserted, update, componentupdated and unbind. For specific usage, please refer to the official user-defined instruction document. In addition to defining hook functions in the form of objects, the official also provides a function abbreviation, such as:
{ directives: { 'color-swatch': function(el, binding) { el.style.backgroundColor = binding.value } } }
The writing of the function will trigger the same behavior in the bind and update hooks, and do not care about other hooks. This behavior is the defined function. Therefore, when normalizing directives, the writing of functions will assign behavior to bind and update hooks.
function normalizeDirectives (options: Object) { const dirs = options.directives if (dirs) { for (const key in dirs) { const def = dirs[key] // Function shorthand will eventually be converted to object form if (typeof def === 'function') { dirs[key] = { bind: def, update: def } } } } }
Function cache
In the above code, there is a code to optimize the performance, which is worthy of reference and learning in future development.
When converting parameters to hump format, the inverted value will be cached after each call to the function. The next time, the value in the cache will be used first instead of re executing the function to improve performance. This is a typical optimization of space for time, and it is also a classic application of partial function
export function cached<F: Function> (fn: F): F { const cache = Object.create(null) // Create an empty object cache data return (function cachedFn (str: string) { const hit = cache[str] return hit || (cache[str] = fn(str)) // If there is a cache, the cache is used. If there is no cache, the function is executed to obtain the result }: any) } const camelizeRE = /-(\w)/g // Convert the writing of a-b form into hump form aB // The cached function is called here to cache the conversion results export const camelize = cached((str: string): string => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') })
summary
In the above three methods, respectively normalize the projects input directives, and the three options will eventually be converted into the object format.