[vue源码05] - Vue.extend

导航

[[深入01] 执行上下文](https://juejin.im/post/684490...)
[[深入02] 原型链](https://juejin.im/post/684490...)
[[深入03] 继承](https://juejin.im/post/684490...)
[[深入04] 事件循环](https://juejin.im/post/684490...)
[[深入05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...)
[[深入06] 隐式转换 和 运算符](https://juejin.im/post/684490...)
[[深入07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...)
[[深入08] 前端安全](https://juejin.im/post/684490...)
[[深入09] 深浅拷贝](https://juejin.im/post/684490...)
[[深入10] Debounce Throttle](https://juejin.im/post/684490...)
[[深入11] 前端路由](https://juejin.im/post/684490...)
[[深入12] 前端模块化](https://juejin.im/post/684490...)
[[深入13] 观察者模式 发布订阅模式 双向数据绑定](https://juejin.im/post/684490...)
[[深入14] canvas](https://juejin.im/post/684490...)
[[深入15] webSocket](https://juejin.im/post/684490...)
[[深入16] webpack](https://juejin.im/post/684490...)
[[深入17] http 和 https](https://juejin.im/post/684490...)
[[深入18] CSS-interview](https://juejin.im/post/684490...)
[[深入19] 手写Promise](https://juejin.im/post/684490...)
[[深入20] 手写函数](https://juejin.im/post/684490...)

[[react] Hooks](https://juejin.im/post/684490...)

[[部署01] Nginx](https://juejin.im/post/684490...)
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...)
[[部署03] gitlab-CI](https://juejin.im/post/684490...)

[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...)
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...)
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...)
[[源码] Redux React-Redux01](https://juejin.im/post/684490...)
[[源码] axios ](https://juejin.im/post/684490...)
[[源码] vuex ](https://juejin.im/post/684490...)
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...)
[[源码-vue02] computed 响应式 - 初始化,访问,更新过程 ](https://juejin.im/post/684490...)
[[源码-vue03] watch 侦听属性 - 初始化和更新 ](https://juejin.im/post/684490...)
[[源码-vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490...)
[[源码-vue05] Vue.extend ](https://juejin.im/post/684490...)

[[源码-vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790...)

前置知识

一些单词

built-in tag:内置标签

reserved:保留
( tag is reserved so that it cannot be registered as a component 如果是保留标签,不能组件名 )

specification:规范
(  html5 specification HTML5规范 )

further:进一步
( allow further extension/mixin/plugin usage 允许进一步扩展... )

(1) 组件注册

  • 全局注册 和 局部注册
  • (1) 全局注册

    • Vue.component( id, [definition] )

      • 参数

        • id:string类型,可以是 MyComponent 或 my-component
        • definition:可选,函数或对象

          • data 必须是函数
          • 不包含 el
      • 作用

        • 注册 或 获取 全局组件
        • 全局注册的组件能在
      • 注意点:

        • definition 对象中的 ( data ) 必须是 ( 函数 ),这样每个组件实例才能维护一份返回对象的独立拷贝
        • id 可以是 MyComponent 或 my-component 这两种写法的字符串
        • 全局注册的组件可以供所有子组件使用
  • (2) 局部注册

    • 在new Vue()的参数对象中通过 components 属性对象进行局部注册
  • 案例123

  • // 注册组件,传入一个扩展过的构造器
    Vue.component('my-component', Vue.extend({ / ... / }))

    // 注册组件,传入一个选项对象 (自动调用 Vue.extend)
    Vue.component('my-component', { / ... / })

    // 获取注册的组件 (始终返回构造器)
    var MyComponent = Vue.component('my-component')


  • Base/BaseButton.ts
    // 全局注册组件
    // 1. 这里是 ts 文件
    // 2. 如果是 .vue 文件可以使用 webpack 的 require.context
    Vue.component('BaseButton', {
    data() {
    return {

    message: '这是一个基础组件-button'

    }
    },
    template: `


    BaseButton

    {{message}}


    `,
    })
    // 注意:在vue-cli3中需要在vue.config.js中配置 ( runtimeCompiler: true ) 表示开启runtime+compiler版本
    // vue.config.js
    module.exports = {
    runtimeCompiler: true, // runtime + compiler 版本
    }


  • Base
    / BaseButton.ts ------------------ 简单的 Vue.component 全局注册 BaseButton 组件
    / BaseButton.vue ----------------- 利用 require.context 实现 Base 文件夹中的所有组件的自动化全局注册
    / index.ts ----------------------- 自动化全局注册逻辑

    index.ts如下

    import Vue from 'vue'
    const requireContext = require.context('.', false, /.vue$/)
    requireContext.keys().forEach(fileName => {
    const componentModule = requireContext(fileName)
    const component = componentModule.default
    Vue.component(component.name, component)
    })

    require.context在vue中的使用官网案例: https://cn.vuejs.org/v2/guide/components-registration.html

(2) Vue.extend( options ) - api

  • 参数

    • options:一个包含组件选项对象
    • 注意:

      • options.data 必须是一个函数
      • Vue.component()Vue.extend() 的参数对象中的 data 都必须是一个 函数
  • 用法

    • 使用基础的 Vue 构造器,创建一个子类
  • 案例 ( 封装一个全局基础toast组件 )

    • toast是一个基础组件,多个地方会用到,所以不要在每个用到的组件中import再在components中注册,而是挂在到vue.prototype上
    • toast的组件不放在vue项目的DOM根节点中,因为会受到路由的影响,而是独立的节点
    Base全局基础组件
    目录结构
    src
      /components
          /base
              / index.js
              / toast.vue
  • src/components/base/toast.vue

  • 正常的写一个展示的toast组件
  • toast组件中的data可以通过Vue.extend生成的子类的实例的参数对象中的data来修改
    [src/components/base/toast.vue]


  • src/components/base/index.js

    ---
    第二步:
    [src/components/base/index.js]
    
    import Vue from "vue";
    import Toast from "./toast.vue";
    
    const generatorToast = ({ message, type, duration = 200 }) => {
    const ToastConstructor = Vue.extend(Toast); // ------------------- Vue.extend()生成Vue子类
    const toastInstance = new ToastConstructor({ // ------------------ new子类,生成组件实例
      el: document.createElement("div"), // -------------------------- 组件挂在节点
      data() { // ---------------------------------------------------- 将和 Toast 组件中的 data 合并
        return {
          message,
          type,
          show: true,
          fade: true,
        };
      },
    });
    setTimeout(() => {
      toastInstance.fade = false; // -------------------------------- 动画,提前执行
    }, duration - 500);
    setTimeout(() => {
      toastInstance.show = false; // -------------------------------- 显示隐藏
    }, duration);
    document.body.appendChild(toastInstance.$el); // ---------------- 组件挂在位置
    };
    
    
    export default { // ----------------------------------------------- 插件对象的install方法
    install() {
      Vue.prototype.$BaseToast = generatorToast;
    },
    };
    // Vue.use(option)
      // option可以是 函数 或者 具有 install方法的对象
      // 这里将 toast/index封装成vue插件,通过Vue.use()注册,即执行install方法
  • src/main.js入口文件

    第三步:
  • 引入src/components/base/index.js
  • Vue.use()注册插件

    import Vue from 'vue'
    import App from './App.vue'
    import Toast from './components/base'

    Vue.config.productionTip = false
    Vue.use(Toast) // ----------------------------------------------- vue插件注册

    new Vue({
    render: h => h(App),
    }).$mount('#app')

  • src/App.vue

    第四步:
    [src/App.vue]
  • 使用

Vue.extend() 源码

  • 一句话总结:使用 基础Vue构造器,创建一个子类

    Vue.extend = function (extendOptions) {
    extendOptions = extendOptions || {}; // 没有传参,就赋值空对象
    var Super = this; // this指的是Vue
    var SuperId = Super.cid; // SuperId => id
    
    var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
    // cachedCtors
    // 用来缓存 Constructor
    // 参数对象中不存在 _Ctor 属性,就将 extendOptions._Ctor = {} 赋值为空对象
    if (cachedCtors[SuperId]) {
      // 存在缓存,直接返回
      return cachedCtors[SuperId]
    }
    
    var name = extendOptions.name || Super.options.name;
    // name
    // 参数对象中不存在 name 属性,就是用父类的options的name属性
    
    if (name) {
      validateComponentName(name);
      // validateComponentName() 验证 name 的合法性
      // 1. 不能是 slot component 这样的内置标签名
      // 2. 不能是 HTML5 的保留关键字标签
    }
    
    var Sub = function VueComponent(options) { // 定义子类
      this._init(options);
    };
    Sub.prototype = Object.create(Super.prototype);
    // 将 ( 子类的prototype的原型 ) 指向 ( 父类prototype )
    // 这样 ( 子类的实例 ) 就能继承 ( 父类prototype ) 上的属性和方法
    
    Sub.prototype.constructor = Sub;
    // 将原型上的constructor属性指向自己,防止修改了原型后 prototype.constructor 不再是指向 Sub
    
    Sub.cid = cid++;
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    );
    // 合并options => 将父类的options和参数对象合并
    
    Sub['super'] = Super;
    // 在子类上挂载 super 属性,指向父类
    
    
    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps$1(Sub);
      // props属性存在,就将props做一层代理
      // initProps方法可以让用户访问this[propName]时相当于访问this._props[propName]
    }
    if (Sub.options.computed) {
      initComputed$1(Sub);
      // 同上
    }
    
    // allow further extension/mixin/plugin usage
    // 继承相关属性
    Sub.extend = Super.extend;
    Sub.mixin = Super.mixin;
    Sub.use = Super.use;
    
    
    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type];
    });
    // 继承 component directive filter
      // var ASSET_TYPES = [
      //   'component',
      //   'directive',
      //   'filter'
      // ];
    
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub;
      // 保存Sub到components属性中
    }
    
    Sub.superOptions = Super.options;
    Sub.extendOptions = extendOptions;
    Sub.sealedOptions = extend({}, Sub.options);
    
    
    // cache constructor
    cachedCtors[SuperId] = Sub; //存在Sub
    
    return Sub
    // 返回 Sub
    };

资料

Vue.extend源码 https://juejin.im/post/684490...
Vue.extend源码 https://zhuanlan.zhihu.com/p/...
toast组件1 https://juejin.im/post/684490...
toast组件2 https://juejin.im/post/684490...

你可能感兴趣的:([vue源码05] - Vue.extend)