一.全局组件的解析

  1. <div id="app">
  2. <my-component></my-component>
  3. <my-component></my-component>
  4. </div>
  5. <script>
  6. Vue.component('my-component',{
  7. template:'<button>点我</button>',
  8. });
  9. let vm = new Vue({
  10. el:'#app'
  11. });
  12. </script>

我们可以通过Vue.component注册全局组件,之后可以在模板中进行使用

  1. export function initGlobalAPI(Vue){
  2. // 整合了所有的全局相关的内容
  3. Vue.options ={}
  4. initMixin(Vue);
  5. // _base 就是Vue的构造函数
  6. Vue.options._base = Vue;
  7. Vue.options.components = {}
  8. // 注册API方法
  9. initAssetRegisters(Vue);
  10. }

1.Vue.component方法

  1. export default function initAssetRegisters(Vue) {
  2. Vue.component = function (id, definition) {
  3. definition.name = definition.name || id;
  4. definition = this.options._base.extend(definition);
  5. this.options['components'][id] = definition;
  6. }
  7. }

理解 Vue.extend() 和 Vue.component() 的区别非常重要。由于 Vue 本身是一个构造函数, Vue.extend() 是一个类继承方法。它用来创建一个 Vue 的子类并返回其构造函数。而另一方面,Vue.component() 是一个类似 Vue.directive() 和 Vue.filter() 的资源注册方法。它作用是建立指定的构造函数与 ID 字符串间的关系,从而让 Vue.js 能在模板中使用它。直接向 Vue.component() 传递 options 时,它会在内部调用 Vue.extend()。

Vue.component内部会调用Vue.extend方法,将定义挂载到Vue.options.components上。这也说明所有的全局组件最终都会挂载到这个变量上

  1. export function initGlobalAPI(Vue){
  2. // 整合了所有的全局相关的内容
  3. Vue.options ={}
  4. initMixin(Vue);
  5. // _base 就是Vue的构造函数
  6. Vue.options._base = Vue;
  7. Vue.options.components = {}
  8. // initExtend
  9. + initExtend(Vue);
  10. // 注册API方法
  11. initAssetRegisters(Vue);
  12. }

2.Vue.extend方法

  1. import {mergeOptions} from '../util/index'
  2. export default function initExtend(Vue) {
  3. let cid = 0;
  4. Vue.extend = function (extendOptions) {
  5. const Super = this;
  6. const Sub = function VueComponent(options) {
  7. this._init(options)
  8. }
  9. Sub.cid = cid++;
  10. Sub.prototype = Object.create(Super.prototype);
  11. Sub.prototype.constructor = Sub;
  12. Sub.options = mergeOptions(
  13. Super.options,
  14. extendOptions
  15. );
  16. return Sub
  17. }
  18. }

extend方法就是创建出一个子类,继承于Vue,并返回这个类

3.属性合并

  1. function mergeAssets(parentVal,childVal){
  2. const res = Object.create(parentVal);
  3. if(childVal){
  4. for(let key in childVal){
  5. res[key] = childVal[key];
  6. }
  7. }
  8. return res;
  9. }
  10. strats.components = mergeAssets;

4.初始化合并

  1. vm.$options = mergeOptions(vm.constructor.options,options);

二.组件的渲染

  1. function makeMap(str) {
  2. const map = {};
  3. const list = str.split(',');
  4. for (let i = 0; i < list.length; i++) {
  5. map[list[i]] = true;
  6. }
  7. return (key)=>map[key];
  8. }
  9. export const isReservedTag = makeMap(
  10. 'a,div,img,image,text,span,input,p,button'
  11. )

在创建虚拟节点时我们要判断当前这个标签是否是组件,普通标签的虚拟节点和组件的虚拟节点有所不同

1.创建组件虚拟节点

  1. export function createElement(vm,tag, data = {}, ...children) {
  2. let key = data.key;
  3. if (key) {
  4. delete data.key;
  5. }
  6. if (typeof tag === 'string') {
  7. if (isReservedTag(tag)) {
  8. return vnode(tag, data, key, children, undefined);
  9. } else {
  10. // 如果是组件需要拿到组件的定义,通过组件的定义创造虚拟节点
  11. let Ctor = vm.$options.components[tag];
  12. return createComponent(vm,tag,data,key,children,Ctor)
  13. }
  14. }
  15. }
  16. function createComponent(vm,tag,data,key,children,Ctor){
  17. // 获取父类构造函数t
  18. const baseCtor = vm.$options._base;
  19. if(isObject(Ctor)){
  20. Ctor = baseCtor.extend(Ctor);
  21. }
  22. data.hook = { // 组件的生命周期钩子
  23. init(){}
  24. }
  25. return vnode(`vue-component-${Ctor.cid}-${tag}`,data,key,undefined,{Ctor,children});
  26. }
  27. function vnode(tag, data, key, children, text, componentOptions) {
  28. return {tag, data, key, children, text, componentOptions}
  29. }

2.创建组件的真实节点

  1. export function patch(oldVnode,vnode){
  2. // 1.判断是更新还是要渲染
  3. if(!oldVnode){
  4. return createElm(vnode);
  5. }else{
  6. // ...
  7. }
  8. }
  9. function createElm(vnode){ // 根据虚拟节点创建真实的节点
  10. let {tag,children,key,data,text} = vnode;
  11. // 是标签就创建标签
  12. if(typeof tag === 'string'){
  13. // createElm需要返回真实节点
  14. if(createComponent(vnode)){
  15. return vnode.componentInstance.$el;
  16. }
  17. vnode.el = document.createElement(tag);
  18. updateProperties(vnode);
  19. children.forEach(child=>{ // 递归创建儿子节点,将儿子节点扔到父节点中
  20. return vnode.el.appendChild(createElm(child))
  21. })
  22. }else{
  23. // 虚拟dom上映射着真实dom 方便后续更新操作
  24. vnode.el = document.createTextNode(text)
  25. }
  26. // 如果不是标签就是文本
  27. return vnode.el;
  28. }
  1. function createComponent(vnode) {
  2. let i = vnode.data;
  3. if((i = i.hook) && (i = i.init)){
  4. i(vnode);
  5. }
  6. if(vnode.componentInstance){
  7. return true;
  8. }
  9. }

调用init方法,进行组件的初始化

  1. data.hook = {
  2. init(vnode){
  3. let child = vnode.componentInstance = new Ctor({});
  4. child.$mount(); // 组件的挂载
  5. }
  6. }
  1. 1.给组件创建一个构造函数, 基于Vue
  2. 2.开始生成虚拟节点, 对组件进行特殊处理 data.hook = { init () {}}
  3. 3.生成dom元素, 如果当前虚拟节点少有hook.init属性,说明是组建
  4. 4.对组件进行 new 组件() .$mount() => vm.$el
  5. 5.将组件的$el 插入到父容器中 (父组件)