Deep Dive into Vue’s mergeOptions: Normalizing Props, Inject, and Directives
This article explains how Vue’s mergeOptions function merges constructor and instance options, detailing the normalization of props, inject, and directives, and how extends and mixins are recursively combined to produce the final component configuration.
mergeOptions Function
The
mergeOptionsfunction combines the options defined on a constructor with the options passed during component instantiation, producing a new options object for the component.
<code>export function mergeOptions(
parent: Object, // options on the constructor
child: Object, // options passed at instantiation
vm?: Component // current instance
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField(key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
</code> checkComponents(child)validates component names.
If
childis a function, its
optionsproperty is used as the child options.
normalizeProps(child, vm),
normalizeInject(child, vm)and
normalizeDirectives(child)convert props, inject, and directives into object formats.
normalizeProps
Vue supports two prop declaration styles: an array of strings or an object. The
normalizePropsfunction converts both forms into a standardized object where each key is camel‑cased and its value describes the prop type.
<code>// Array form
Vue.component('test-component', {
props: ['testData'],
template: '<span>{{ testData }}</span>'
})
// Object form
Vue.component('test-component', {
props: {
testData: {
type: String,
default: ''
}
},
template: '<span>{{ testData }}</span>'
})
</code> <code>function normalizeProps(options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
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)) {
for (const key in props) {
val = props[key]
name = camelize(key)
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
}
</code>Resulting transformations:
Array form →
{ testData: { type: null } }Object form →
{ testData: { type: String, default: '' } }normalizeInject
<code>/**
* Normalize all injections into Object‑based format
*/
function normalizeInject(options: Object, vm: ?Component) {
const inject = options.inject
if (!inject) return
const normalized = options.inject = {}
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] }
}
} else if (isPlainObject(inject)) {
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)
}
}
</code>Examples:
<code>// Provider component
var Provider = {
provide: { foo: 'bar' }
}
// Child component using array syntax
var Child = {
inject: ['foo'],
created () { console.log(this.foo) } // => "bar"
}
// Child component using object syntax
const Child = {
inject: {
foo: { from: 'bar', default: 'foo' }
}
}
</code>After normalization both forms become:
<code>// Array form
{ foo: { from: 'foo' } }
// Object form
{ foo: { from: 'bar', default: 'foo' } }
</code>normalizeDirectives
<code>/**
* Normalize raw function directives into object format.
*/
function normalizeDirectives(options: Object) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def }
}
}
}
}
</code>Directive registration can be object‑based or function‑based:
<code>// Object form
Vue.directive('my-directive', {
bind() {},
inserted() {},
update() {},
componentUpdated() {},
unbind() {}
})
// Function form (treated as bind & update)
Vue.directive('my-directive', function () {
// will be called for bind and update
})
</code>After normalization the function form becomes:
<code>{
'my-directive': { bind: function () {}, update: function () {} }
}
</code>Handling extends and mixins
When the child options contain
extendsor
mixins,
mergeOptionsrecursively merges those objects into the parent options before processing the rest of the child options.
<code>const childComponent = Vue.component('child', {
mixins: [myMixin],
extends: myComponent,
// other options …
})
const myMixin = {
created() { this.hello() },
methods: { hello() { console.log('hello from mixin') } }
}
const myComponent = {
mounted() { this.goodbye() },
methods: { goodbye() { console.log('goodbye from mixin') } }
}
</code>The merge process extracts lifecycle hooks and methods from both
mixinsand
extendsand combines them with the component’s own options.
Key Takeaways
Props are normalized to an object format:
{type: null}or
{type: Value}.
Inject is normalized to an object format:
{from: Value}(with optional
default).
Directives are normalized so that function forms become
bindand
updatemethods.
Options with
extendsand
mixinsare recursively merged into the parent options.
References
Vue 2.0 source code
WeDoctor Frontend Technology
Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.