组件化的合并配置 - 学习vue源码系列 3.2

简介: 组件化的合并配置 - 学习vue源码系列 3.2

组件化的合并配置 - 学习vue源码系列 3.2


决定跟着黄轶老师的 vue2 源码课程好好学习下vue2的源码,学习过程中,尽量输出自己的所得,提高学习效率,水平有限,不对的话请指正~

vue 的源码clone 到本地,切换到分支2.6

Introduction

组件化渲染的那块,我个人觉得挺复杂的,我还没有捋顺,我决定先往后看,看完整体,再来磕硬骨头。

其实 vue 也有点配置为王的感觉,它的配置就是options

后期组件的各种相关操作,都围绕options展开。

本篇着重看看,vue是怎么处理options的,最最重点的是,怎么合并各个options的。

先看 demo

尝试先自己想想,这些钩子函数里的这些打印顺序如何。

<div id="app"></div>
<script src="/Users/zhm/mygit/vue/dist/vue.js"></script>
<script>
  const log = console.log;
  // 全局mixin
  Vue.mixin({
    created() {
      log("mixin created");
    },
  });
  // 子组件实例
  let childCompInstance = null;
  // 子组件
  let childComp = {
    name: "MSG",
    template: "<div>{{msg}}</div>",
    created() {
      childCompInstance = this;
      log("child created");
    },
    data: () => ({ msg: "Hello Vue" }),
  };
  // 根组件实例
  let vueInstance = new Vue({
    el: "#app",
    created() {
      log("parent created");
    },
    render: (h) => h(childComp),
  });
  log("Vue构造器上的options", Vue.options);
  log("Vue实例的options", vueInstance, vueInstance.$options);
  log(
    "VueComponent实例的options",
    childCompInstance,
    childCompInstance.$options,
    childCompInstance.$options.__proto__
  );
</script>

公布答案:

mixin created
parent created
mixin created
child created

其实定义在 Vue 的 mixin 里的options,会合并Vue构造器的的options里面。

用构造器创建实例的时候,构造器上面的options会和实例的options再次合并。

组件构造器是 Vue 的子类,合并的时候,主要的optionschildCompInstance.$options.__proto__里。

看下,例子里后面的打印:

网络异常,图片无法展示
|

Vue.mixin 和 Vue 构造器的 options 合并

先说下Vue.mixin是个函数,其传入的options,首先和 Vue 构造器上面的options合并。

Vue.mixin执行之后,Vue 构造器上面的options就有了其对应的值。

先来个超简单的示意:

/** vue源码 开始 */
function Vue(options) {
}
Vue.options = {};
// 将两个options合并成一个options
const mergeOptions = (parentOptions, childOptions) => {
  let options = {};
  // 生命周期的钩子合并的时候都是数组
  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源码 结束 */
// 使用的例子
Vue.mixin({
  created() {
    console.log("mixin created");
  },
});
// {created:[x]}
console.log(Vue.options);

总的来说就是做着上面的事情,然后看 vue 的真正源码

// src/core/global-api/mixin.js
export function initMixin(Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    // mixin就是{ created(){} }, this指的是Vue构造器,因为使用的时候是Vue.mixin
    this.options = mergeOptions(this.options, mixin);
    return this;
  };
}
// src/core/util/options.js
export function mergeOptions(
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // parent就是Vue构造器的options,child就是{ created(){} }
  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就是Vue构造器上面的options,key就是每项键,key不一样合并的策略不一样,先处理parent
  for (key in parent) {
    mergeField(key);
  }
  // child就是 { created(){} },在处理child里的(parent没有的)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;
}

Vue 实例的时候

Vue 构造器上面的options会和实例里参数options合并。 当然合并的逻辑是相似的。 拿这个例子来说,会变成created:[fn1,fn2],注意构造器的在前面,参数的在后面。

// options就是类似例子的{el:'#app',....}
Vue.prototype._init = function (options) {
  var vm = this;
  // 将各种选项合并,合并之后就是{created:[fn1,fn2]}
  vm.$options = mergeOptions(
    // 这里vm.constructor就是Vue,Vue.options{created:[fn1]}
    resolveConstructorOptions(vm.constructor),
    // {created:fn2}
    options || {},
    vm
  );
};

这里可以简单看下Vue上面的静态属性options

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
  // 将内置组件扩展到Vue.options.components上,keepAlive transition
  extend(Vue.options.components, builtInComponents)
  // ...
}

VueComponent实例的时候

由于组件的构造函数是通过Vue.extend继承自Vue的。

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
}

很显然,组件类的options是组件对象和Vueoptions合并的。

组件初始化的时候,

// options有以下属性
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)
}

vnode.componentOptions.Ctor 就是指向 Vue.extend 的返回值 Sub。执行new的话,就会再次执行this._init(options).

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  // 组件类的options和Vue类的options并不相同。
  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
  }
}

initInternalComponent 方法执行,相当于 vm.$options = Object.create(Sub.options)。 此时,vm.$options如下:

vm.$options = {
  parent: Vue /*父Vue实例*/,
  propsData: undefined,
  _componentTag: undefined,
  _parentVnode: VNode /*父VNode实例*/,
  _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>'
  }
}

走一遍看看调试

vue.js3处打个断点:

  • Vue.mixin打个断点
  • Vue.prototype._initoptions处打个断点
  • function mergeOptions打个断点

先看看mixin:

网络异常,图片无法展示
|

Vue.mixin之后,Vue的options合并了Vue.mixin的参数,变成这样:

{
  components: {}
  created: [ƒ]
  directives: {}
  filters: {}
  _base: ƒ Vue(options)
}

new Vue(options)会再和Vue的options合并,VueComponentoptions也会和Vue的options合并:

网络异常,图片无法展示
|

vue实例的$options是用户传的optionsVueoptions的合并,此时vue实例的$options

{
  components: {}
  created: (2) [ƒ, ƒ]
  directives: {}
  el: "#app"
  filters: {}
  render: (h) => h(childComp)
  _base: ƒ Vue(options)
  _isVue: true
  _uid: 0
}

VueComponent组件实例的options是和Vue的options合并:

网络异常,图片无法展示
|

显然组件的合并是先走了Vue.extend,然后走了initInternalComponent,最后,组件实例的options就是

_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: Vue, …}
  propsData: undefined
  render: ƒ anonymous( )
  staticRenderFns: []
  _componentTag: undefined
  _parentListeners: undefined
  _parentVnode: VNode {tag: "vue-component-1-MSG", data: {…}, children: undefined, text: undefined, elm: div, …}

总结

Vue 初始化阶段对于 options 的合并有 2 种方式:

  • 外部初始化 Vue 通过 mergeOptions 的过程,合并完的结果保留在 vm.$options 中。
  • 子组件初始化过程通过 initInternalComponent 方式,比mergeOptions的方式要快

纵观一些库、框架的设计几乎都是类似的,

  • 自身定义了一些默认配置,
  • 同时又可以在初始化阶段传入一些自定义配置,
  • 然后 merge配置,来达到定制化不同需求的目的。

引用

目录
相关文章
|
1月前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
7天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
1月前
|
JavaScript 前端开发 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
1月前
|
存储 JavaScript 前端开发
介绍一下Vue的核心功能
介绍一下Vue的核心功能
|
JavaScript
|
1月前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
41 1
vue学习第一章
|
1月前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
30 1
|
1月前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
38 1
vue学习第四章
|
1月前
|
JavaScript 前端开发 算法
vue学习第7章(循环)
欢迎来到瑞雨溪的博客,一名热爱JavaScript和Vue的大一学生。本文介绍了Vue中的v-for指令,包括遍历数组和对象、使用key以及数组的响应式方法等内容,并附有综合练习实例。关注我,将持续更新更多优质文章!🎉🎉🎉
25 1
vue学习第7章(循环)
|
1月前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
30 1
vue学习第九章(v-model)

热门文章

最新文章