乐趣区

关于javascript:vue源码05-Vueextend

导航

[[深刻 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) 全局注册

    • <font color=red>Vue.component(id, [definition] )</font>

      • 参数

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

          • <font color=red>data 必须是函数 </font>
          • <font color=red> 不蕴含 el</font>
      • 作用

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

        • 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: `
    <div>
    <div>BaseButton</div>
    <div>{{message}}</div>
    </div>
    `,
    })
    // 留神:在 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 结构器,创立一个子类
  • 案例 (<font color=red> 封装一个全局根底 toast 组件 </font>)

    • 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]
    <template>
    <div
    class=”base-toast”
    v-if=”show”
    :class=”[animateFn, backgrondType]”

    {{message}}</div>
    </template>
    <script>
    export default {
    name: “BaseToast”,
    data() {
    return {

    message: "", // toast 显示内容
    show: true, // 显示暗藏
    fade: true, // 显示暗藏动画
    type: "",
    typeArr: ['error', 'success']

    };
    },
    computed: {
    backgrondType() {

      return 'toast-' + this.typeArr.find(type)  

    },
    animateFn() {

      return this.fade ? 'fadein' : 'fadeout'

    }
    }
    };
    </script>
    <style lang=”css”>
    .base-toast {
    padding: 10px;
    background: rgba(0, 0, 0, 0.5);
    position: absolute;
    left: 50%;
    top: 10px;
    transform: translate(-50%, 0);
    display: inline-block;
    margin: 0 auto;
    text-align: center;
    }
    .fadein {
    animation: animation_fade_in 0.5s;
    }
    .fadeout {
    animation: animation_fade_out 0.5s;
    }
    @keyframes animation_fade_in {
    from {
    opacity: 0;
    }
    to {
    opacity: 1;
    }
    }
    @keyframes animation_fade_out {
    from {
    opacity: 1;
    }
    to {
    opacity: 0;
    }
    }
    .toast-success {
    background: green;
    }
    .toast-error {
    background: red;
    }
    </style>

  • 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]
  • 应用
    <template>
    <div id=”app”>
    <HelloWorld msg=”Welcome to Your Vue.js App”/>
    </div>
    </template>
    <script>
    import HelloWorld from ‘./components/HelloWorld.vue’
    export default {
    name: ‘App’,
    components: {
    HelloWorld
    },
    mounted() {
    this.$BaseToast({// ————————————— 通过 this.$BaseToast() 调用

    message: '111',
    duration: 3000,
    type: 'error'

    })
    }
    }
    </script>

Vue.extend() 源码

  • 一句话总结:<font color=red> 应用 根底 Vue 结构器,创立一个子类 </font>

    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…

退出移动版