Рукописный исходный код Vue2.0 (8) - компонентный принцип

Vue.js
Рукописный исходный код Vue2.0 (8) - компонентный принцип

предисловие

В этой статье в основном написан исходный код Vue2.0 от руки.Компонент Принцип

В предыдущей статье мы в основном представили VueПринцип миксинаЭто API, который объединяет ядро ​​параметров инициализации Vue. Все знают, что основной особенностью Vue является компонентизация. В этой статье в основном представлен весь процесс создания и рендеринга компонентов. Среди них API Vue.extend является ядром создания компонентов. .

Для людей:

1. Хотите иметь глубокое понимание исходного кода vue для лучшего ежедневного развития бизнеса

2. Хотите владеть исходным кодом vue framework в резюме (больше не боитесь вопросов интервьюера о серийных убийцах, ха-ха)

3. Учащиеся, у которых нет времени смотреть официальный исходный код или им трудно понять с первого взгляда исходный код


текст

<script>
  // 全局组件
  Vue.component("parent-component", {
    template: `<div>我是全局组件</div>`,
  });
  // Vue实例化
  let vm = new Vue({
    el: "#app",
    data() {
      return {
        aa: 1,
      };
    },
    // render(h) {
    //   return h('div',{id:'a'},'hello')
    // },
    template: `<div id="a">
      hello 这是我自己写的Vue{{aa}}
      <parent-component><parent-component>
      <child-component></child-component>
      </div>`,
    // 局部组件
    components: {
      "child-component": {
        template: `<div>我是局部组件</div>`,
      },
    },
  });
</script>

Вышеприведенное демонстрирует использование самых основных глобальных компонентов и локальных компонентов.По сути, каждый из наших компонентов является подклассом, который наследуется от Vue и может использовать метод прототипа Vue.

1. Глобальная регистрация компонентов

// src/global-api/index.js

import initExtend from "./initExtend";
import initAssetRegisters from "./assets";
const ASSETS_TYPE = ["component", "directive", "filter"];
export function initGlobalApi(Vue) {
  Vue.options = {}; // 全局的组件 指令 过滤器
  ASSETS_TYPE.forEach((type) => {
    Vue.options[type + "s"] = {};
  });
  Vue.options._base = Vue; //_base指向Vue

  initExtend(Vue); // extend方法定义
  initAssetRegisters(Vue); //assets注册方法 包含组件 指令和过滤器
}

Метод initGlobalApi в основном используется для регистрации глобальных методов Vue, таких как ранее написанный Vue.Mixin и сегодняшний Vue.extend Vue.component и т. д.

// src/global-api/asset.js

const ASSETS_TYPE = ["component", "directive", "filter"];
export default function initAssetRegisters(Vue) {
  ASSETS_TYPE.forEach((type) => {
    Vue[type] = function (id, definition) {
      if (type === "component") {
        //   this指向Vue
        // 全局组件注册
        // 子组件可能也有extend方法  VueComponent.component方法
        definition = this.options._base.extend(definition);
      }
      this.options[type + "s"][id] = definition;
    };
  });
}

this.options._base относится к Vue, так называемый глобальный компонент — использовать метод Vue.extend для обработки входящих параметров и их монтирования на Vue.options.components.

2. Определение Vue.extend

//  src/global-api/initExtend.js

import { mergeOptions } from "../util/index";
export default function initExtend(Vue) {
  let cid = 0; //组件的唯一标识
  // 创建子类继承Vue父类 便于属性扩展
  Vue.extend = function (extendOptions) {
    // 创建子类的构造函数 并且调用初始化方法
    const Sub = function VueComponent(options) {
      this._init(options); //调用Vue初始化方法
    };
    Sub.cid = cid++;
    Sub.prototype = Object.create(this.prototype); // 子类原型指向父类
    Sub.prototype.constructor = Sub; //constructor指向自己
    Sub.options = mergeOptions(this.options, extendOptions); //合并自己的options和父类的options
    return Sub;
  };
}

Основная идея Vue.extend заключается в использовании прототипного метода наследования для возврата подкласса Vue и использовании mergeOptions для объединения параметров входящего компонента с параметрами родительского класса.

3. Стратегия объединения компонентов

// src/init.js

Vue.prototype._init = function (options) {
  const vm = this;
  vm.$options = mergeOptions(vm.constructor.options, options); //合并options
};

Помните, когда мы объединяли параметры при инициализации Vue?Глобальные компоненты монтируются в Vue.options.components, а локальные компоненты также определяются в своих собственных options.components.Итак, как мы справляемся со слиянием глобальных и локальных компонентов?

// src/util/index.js

const ASSETS_TYPE = ["component", "directive", "filter"];
// 组件 指令 过滤器的合并策略
function mergeAssets(parentVal, childVal) {
  const res = Object.create(parentVal); //比如有同名的全局组件和自己定义的局部组件 那么parentVal代表全局组件 自己定义的组件是childVal  首先会查找自已局部组件有就用自己的  没有就从原型继承全局组件  res.__proto__===parentVal
  if (childVal) {
    for (let k in childVal) {
      res[k] = childVal[k];
    }
  }
  return res;
}

// 定义组件的合并策略
ASSETS_TYPE.forEach((type) => {
  strats[type + "s"] = mergeAssets;
});

Здесь снова используется метод прототипного наследования для слияния компонентов: внутри компонента он сначала ищет свои собственные локально определенные компоненты, а если не находит, то ищет компоненты, определенные в прототипе.

4. Создайте компонент Vnode

// src/util/index.js

export function isObject(data) {
  //判断是否是对象
  if (typeof data !== "object" || data == null) {
    return false;
  }
  return true;
}

export function isReservedTag(tagName) {
  //判断是不是常规html标签
  // 定义常见标签
  let str =
    "html,body,base,head,link,meta,style,title," +
    "address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section," +
    "div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul," +
    "a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby," +
    "s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video," +
    "embed,object,param,source,canvas,script,noscript,del,ins," +
    "caption,col,colgroup,table,thead,tbody,td,th,tr," +
    "button,datalist,fieldset,form,input,label,legend,meter,optgroup,option," +
    "output,progress,select,textarea," +
    "details,dialog,menu,menuitem,summary," +
    "content,element,shadow,template,blockquote,iframe,tfoot";
  let obj = {};
  str.split(",").forEach((tag) => {
    obj[tag] = true;
  });
  return obj[tagName];
}

Обращение — служебный метод, который будет использоваться в процессе создания компонента Vnode.

// src/vdom/index.js

import { isObject, isReservedTag } from "../util/index";
// 创建元素vnode 等于render函数里面的 h=>h(App)
export function createElement(vm, tag, data = {}, ...children) {
  let key = data.key;

  if (isReservedTag(tag)) {
    // 如果是普通标签
    return new Vnode(tag, data, key, children);
  } else {
    // 否则就是组件
    let Ctor = vm.$options.components[tag]; //获取组件的构造函数
    return createComponent(vm, tag, data, key, children, Ctor);
  }
}

function createComponent(vm, tag, data, key, children, Ctor) {
  if (isObject(Ctor)) {
    //   如果没有被改造成构造函数
    Ctor = vm.$options._base.extend(Ctor);
  }
  // 声明组件自己内部的生命周期
  data.hook = {
    // 组件创建过程的自身初始化方法
    init(vnode) {
      let child = (vnode.componentInstance = new Ctor({ _isComponent: true })); //实例化组件
      child.$mount(); //因为没有传入el属性  需要手动挂载 为了在组件实例上面增加$el方法可用于生成组件的真实渲染节点
    },
  };

  // 组件vnode  也叫占位符vnode  ==> $vnode
  return new Vnode(
    `vue-component-${Ctor.cid}-${tag}`,
    data,
    key,
    undefined,
    undefined,
    {
      Ctor,
      children,
    }
  );
}

Перепишите метод createElement. Для неординарных html-тегов нужно сгенерировать компонент Vnode. Передать Ctor и дочерние элементы в качестве последнего параметра componentOptions для Vnode

Здесь нужно обратить внимание на метод data.hook.init компонента, мы вручную вызываем метод child.$mount(), этот метод может генерировать реальный дом компонента и монтировать его к собственному атрибуту $el. Если у вас есть какие-либо вопросы здесь, вы можете проверить предыдущую статью редактораРукописный исходный код Vue2.0 (3) — принцип начального рендеринга

5. Рендеринг компонента реального узла

// src/vdom/patch.js

// patch用来渲染和更新视图
export function patch(oldVnode, vnode) {
  if (!oldVnode) {
    // 组件的创建过程是没有el属性的
    return createElm(vnode);
  } else {
    //   非组件创建过程省略
  }
}

// 判断是否是组件Vnode
function createComponent(vnode) {
  // 初始化组件
  // 创建组件实例
  let i = vnode.data;
  //   下面这句话很关键 调用组件data.hook.init方法进行组件初始化过程 最终组件的vnode.componentInstance.$el就是组件渲染好的真实dom
  if ((i = i.hook) && (i = i.init)) {
    i(vnode);
  }
  // 如果组件实例化完毕有componentInstance属性 那证明是组件
  if (vnode.componentInstance) {
    return true;
  }
}

// 虚拟dom转成真实dom
function createElm(vnode) {
  const { tag, data, key, children, text } = vnode;
  //   判断虚拟dom 是元素节点还是文本节点
  if (typeof tag === "string") {
    if (createComponent(vnode)) {
      // 如果是组件 返回真实组件渲染的真实dom
      return vnode.componentInstance.$el;
    }
    //   虚拟dom的el属性指向真实dom 方便后续更新diff算法操作
    vnode.el = document.createElement(tag);
    // 解析虚拟dom属性
    updateProperties(vnode);
    // 如果有子节点就递归插入到父节点里面
    children.forEach((child) => {
      return vnode.el.appendChild(createElm(child));
    });
  } else {
    //   文本节点
    vnode.el = document.createTextNode(text);
  }
  return vnode.el;
}

Судя по тому, принадлежит ли он компоненту Vnode, верните визуализированный компонент в реальный dom ==>vnode.componentInstance.$el

6. Интеллект-карта компонентов

Vue2.0源码-组件.png

резюме

На данный момент исходный код компонента Vue завершен. Фактически каждый компонент является экземпляром Vue. Он будет проходить через метод инициализации init. Перед изучением компонентов рекомендуется понять компоненты из предыдущей серии. легче понять.Вы можете посмотреть на карту ума и написать ее самостоятельно. После того, как вы встретите основной код, вы можете оставить комментарий и оставить сообщение, когда столкнетесь с чем-то, что вы не понимаете, или у вас возникнут споры.

Наконец, если вы найдете эту статью полезной, помнитеКак СанлианБольшое спасибо!

Ссылка на сериал (будет обновлена ​​позже)

Группа передовых рыболовных технологий Brother Shark

Приветствую всех на технических биржахСвязь