Decided to follow Teacher Huang Yi’s VUE2 source course good study vuE2 source code, learning process, try to output their own income, improve learning efficiency, level is limited, wrong words please correct ~
Vue source clone to local, switch to branch 2.6.
Introduction
The piece of componentized rendering, I personally think, is quite complicated and I haven’t sorted it out yet. I decided to look back and see the whole before I hit the hard part.
In fact, vue is also a bit of a configuration king, its configuration is options.
Various operations related to later components revolve around options.
This article focuses on how Vue handles options and, most importantly, how to merge them.
Look at the demo
Try to think for yourself about the order in which the hook functions are printed.
<div id="app"></div>
<script src="/Users/zhm/mygit/vue/dist/vue.js"></script>
<script>
const log = console.log;
/ / global mixins
Vue.mixin({
created() {
log("mixin created"); }});// Subcomponent instance
let childCompInstance = null;
/ / child component
let childComp = {
name: "MSG".template: "<div>{{msg}}</div>".created() {
childCompInstance = this;
log("child created");
},
data: () = > ({ msg: "Hello Vue"})};// Root component instance
let vueInstance = new Vue({
el: "#app".created() {
log("parent created");
},
render: (h) = > h(childComp),
});
log("Options on the Vue constructor", Vue.options);
log("Options for the Vue instance", vueInstance, vueInstance.$options);
log(
"Options for a VueComponent instance",
childCompInstance,
childCompInstance.$options,
childCompInstance.$options.__proto__
);
</script>
Copy the code
Answer:
mixin created
parent created
mixin created
child created
Copy the code
Options defined in the Vue mixin are merged into the Vue constructor options.
When you create an instance using a constructor, the options on the constructor are merged with the options on the instance.
The component constructor is a subclass of Vue. When merged, the main options are in childCompInstance.$options.
Take a look at the print at the end of the example:
Mixin and options for the Vue constructor
Mixin is a function, and the options passed in are first merged with the options in the Vue constructor.
After vue. mixin is executed, options on the Vue constructor have their corresponding values.
Let’s start with a very simple gesture:
/** vue source */
function Vue(options) {
}
Vue.options = {};
// Merge the two options into one
const mergeOptions = (parentOptions, childOptions) = > {
let options = {};
// Lifecycle hooks are merged as arrays
for (let key in childOptions) {
if (key === "created") {
const parentCreated = Array.isArray(parentOptions.created)
? parentOptions.created
: parentOptions.created
? [parentOptions.created]
: [];
const childCreated = Array.isArray(childOptions.created) ? childOptions.created : [childOptions.created]; options.created = [...parentCreated, ...childCreated]; }}return options;
};
Vue.mixin = function mixin(options) {
this.options = mergeOptions(Vue.options, options);
return this
};
/** vue source end */
// Examples of use
Vue.mixin({
created() {
console.log("mixin created"); }});// {created:[x]}
console.log(Vue.options);
Copy the code
Basically do the above things, and then look at the real source of Vue
// src/core/global-api/mixin.js
export function initMixin(Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
// Mixin is {created(){}}, this refers to the Vue constructor, because when used it is vue.mixin
this.options = mergeOptions(this.options, mixin);
return this;
};
}
Copy the code
// src/core/util/options.js
export function mergeOptions(
parent: Object,
child: Object, vm? : Component) :Object {
// Parent is the Vue constructor options,child is {}}
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.
// Only merged options has the _base property.
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;
// Parent is the options on the Vue constructor, and key is each key
for (key in parent) {
mergeField(key);
}
// Child is {created(){}}, processing the key in the child (parent does not have)
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;
}
Copy the code
Vue instance
The options on the Vue constructor is merged with the options argument in the instance. Of course the logic of the merge is similar. In this example, it would become created:[fn1,fn2], notice that the constructor is first, and the parameter is second.
// options is similar to {el:'#app',.... }
Vue.prototype._init = function (options) {
var vm = this;
{created:[fn1,fn2]}
vm.$options = mergeOptions(
Options {created:[fn1]}
resolveConstructorOptions(vm.constructor),
// {created:fn2}
options || {},
vm
);
};
Copy the code
Here is a quick look at the static property options on Vue
const ASSET_TYPES = [
'component'.'directive'.'filter'
]
export function initGlobalAPI (Vue: GlobalAPI) {
// ...
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type= > {
Vue.options[type + 's'] = Object.create(null)})// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
// Extend the built-in component to the Vue.options.components, keepAlive Transition
extend(Vue.options.components, builtInComponents)
// ...
}
Copy the code
VueComponent instance
The constructor of a component is inherited from Vue through vue.extend.
Vue.extend = function (extendOptions: Object) :Function {
// ...
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// ...
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// ...
return Sub
}
Copy the code
Obviously, the options of the component class are merged with the options of the component object and Vue.
When the component is initialized,
// Options has the following properties
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
) :Component {
const options: InternalComponentOptions = {
_isComponent: true._parentVnode: vnode,
parent
}
// ...
return new vnode.componentOptions.Ctor(options)
}
Copy the code
Vnode.com ponentOptions. Ctor is pointing to the Vue. Extend the return value of the Sub. If new is executed, this._init(options) is executed again.
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// Options for the component class are different from options for the Vue class.
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
Copy the code
InitInternalComponent method, equivalent to vm.$options = object.create (sub.options). In this case, vm.$options:
vm.$options = {
parent: Vue /* Parent Vue instance */.propsData: undefined._componentTag: undefined._parentVnode: VNode /* Parent VNode instance */._renderChildren:undefined.__proto__: {
components: {},directives: {},filters: {},_base: function Vue(options) {
/ /...
},
_Ctor: {},
created: [
function created() {
console.log('parent created')},function created() {
console.log('child created')},mounted: [
function mounted() {
console.log('child mounted')},data() {
return {
msg: 'Hello Vue'}},template: '<div>{{msg}}</div>'}}Copy the code
Go through the debugging
Make a break point at vue.js3:
Vue.mixin
A breakpointVue.prototype._init
In theoptions
Make a break point atfunction mergeOptions
A breakpoint
First take a look at a mixin:
After Vue. Mixin, Vue’s options merge the parameters of Vue. Mixin to look like this:
{
components: {}
created: [ƒ]
directives: {}
filters: {}
_base: ƒ Vue(options)
}
Copy the code
New Vue(options) will merge with Vue’s options, VueComponent’s options will merge with Vue’s options:
The $options of the vue instance is a combination of the options passed by the user and vue’s options.
{
components: {}
created: (2) / ƒ, ƒdirectives: {}
el: "#app"
filters: {}
render: (h) = > h(childComp)
_base: ƒ Vue (options)_isVue: true
_uid: 0
}
Copy the code
Options for the VueComponent component instance are merged with options for Vue:
Extend, then initInternalComponent, and finally, options for the component instance
_proto__: {components: {MSG: ƒ}
created: (2) / ƒ, ƒdata: () = > ({ msg: "Hello Vue" })
directives: {}
filters: {}
name: "MSG"
template: "<div>{{msg}}</div>"
_Ctor: {0: ƒ}}parent: Vue {_uid: 0._isVue: true.$options: {... },_renderProxy: Proxy._self: the Vue,... }propsData: undefined
render: ƒ anonymous( )
staticRenderFns: []
_componentTag: undefined
_parentListeners: undefined
_parentVnode: VNode {tag: "vue-component-1-MSG".data: {... },children: undefined.text: undefined.elm: div... }Copy the code
conclusion
Options can be merged in two ways during Vue initialization:
- The external Vue was initialized. Procedure
mergeOptions
The process of merging the finished result remains invm.$options
In the. - The child component initialization process passes
initInternalComponent
Way,mergeOptions
Do it fast
Across the board, the design of some libraries and frameworks is almost always similar,
- It defines some default configurations,
- You can also pass in some custom configuration during initialization,
- then
merge
Configuration to achieve the purpose of customizing different requirements.
reference
- Teacher Huang Yi’s VUE2 source decryption course
- Vue.js technology revealed