导航
[[深入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...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。