美文网首页
[vue源码05] - Vue.extend

[vue源码05] - Vue.extend

作者: woow_wu7 | 来源:发表于2021-09-26 11:27 被阅读0次

    导航

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

    [react] Hooks

    [部署01] Nginx
    [部署02] Docker 部署vue项目
    [部署03] gitlab-CI

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

    [源码-vue06] Vue.nextTick 和 vm.$nextTick

    前置知识

    一些单词

    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
    1. 
    // 注册组件,传入一个扩展过的构造器
    Vue.component('my-component', Vue.extend({ /* ... */ }))
    
    // 注册组件,传入一个选项对象 (自动调用 Vue.extend)
    Vue.component('my-component', { /* ... */ })
    
    // 获取注册的组件 (始终返回构造器)
    var MyComponent = Vue.component('my-component')
    
    
    
    --------------
    2. 
    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 版本
    }
    
    
    --------------
    3. 
    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
    第一步:
    1. 正常的写一个展示的toast组件
    2. 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/main.js入口文件]
    1. 引入src/components/base/index.js
    2. 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]
    
    1. 使用
    <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>
    
    
    image

    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/6844904126065688583
    Vue.extend源码 https://zhuanlan.zhihu.com/p/121799032
    toast组件1 https://juejin.im/post/6844903604902428679
    toast组件2 https://juejin.im/post/6844903825711562766

    相关文章

      网友评论

          本文标题:[vue源码05] - Vue.extend

          本文链接:https://www.haomeiwen.com/subject/yddqnltx.html