Vue 高级用法
动画特效
- transition 实现路由切换动画
- App.vue
- Home -> List -> Detail 页面从右往左出现
- Detail -> List -> Home 页面从左往右出现
插槽 - Slot
当某些组件或页面的宏观布局确定,局部子组件需要经常变化的时候,十分适合使用 Slot
父组件和子组件的变量作用域都是创建时的作用域,但是父组件可以向 slot 子组件传递数据
- 默认 slot
- 具名 slot
- 作用域 slot
- 语法糖:#。#defaut
- slot 实现原理
slot 本质上是一个返回 VNode 的函数,一般情况下,Vue 中的组件要渲染到页面上需要经过 template -> render function -> VNode -> DOM 的过程
比如一个带 slot 的组件
Vue.component('button-counter', {
template: '<div> <slot>我是默认内容</slot></div>'
})
new Vue({
el: '#app',
template: '<button-counter><span>我是slot传入内容</span></button-counter>',
components:{buttonCounter}
})
经过 vue 编译,组件渲染函数会变成
(function anonymous(
) {
with(this){
return _c('div',[_t("default",[_v("我是默认内容")])],2)}
})
而这个_t就是 slot 渲染函数
function resolveSlots (
children,
context
) {
if (!children || !children.length) {
return {}
}
var slots = {};
for (var i = 0, l = children.length; i < l; i++) {
var child = children[i];
var data = child.data;
// remove slot attribute if the node is resolved as a Vue slot node
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot;
}
// named slots should only be respected if the vnode was rendered in the
// same context.
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
// 如果slot存在(slot="header") 则拿对应的值作为key
var name = data.slot;
var slot = (slots[name] || (slots[name] = []));
// 如果是tempalte元素 则把template的children添加进数组中,这也就是为什么你写的template标签并不会渲染成另一个标签到页面
if (child.tag === 'template') {
slot.push.apply(slot, child.children || []);
} else {
slot.push(child);
}
} else {
// 如果没有就默认是default
(slots.default || (slots.default = [])).push(child);
}
}
// ignore slots that contains only whitespace
for (var name$1 in slots) {
if (slots[name$1].every(isWhitespace)) {
delete slots[name$1];
}
}
return slots
}
Mixin
本质就是一个 js 对象,它可以包含我们组件中任意功能选项,如 data、components、methods、created、computed等等
我们只要将公用的功能以对象的方式传入 mixins 选项中是,当组件使用 mixins 对象时所有 mixins 对象的选项都将被混入该组件本身的选项中来
在 Vue 中我们可以局部混入和全局混入,全局混入常用来编写插件。
- 当组件存在与 mixin 对象同名的数据的时候,进行递归合并的时候组件的数据会覆盖 mixin 的数据
- 如果相同数据为声明周期钩子的时候,会合并成一个数组,先执行 mixin 钩子,在执行组件的钩子。(mounted / beforeDestroy 都是一样的)
- mixin 实战
- mixin 实现原理
- 优先递归处理 mixins
- 先遍历合并 parent 中的 key,调用 mergeField 方法进行合并,然后再保存在变量 options
- 再遍历 child,合并补上 parent 中没有的 key,调用 mergeField 方法进行合并,保存在变量 options
- 通过 mergeField 函数进行了合并
export function mergeOption (
parent: Object,
child: Object,
vm?: Component
): Object {
// 判断有有没有 mixin 也就是 mixin里面挂 mixin 的情况,有的化递归合并
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {}
let key
for (key in parent) {
// 先遍历 parent 的 key 调对应的 strats[xxx]方法进行合并
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
处理 parent 没有处理的 key
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
其主要逻辑就是合并 mixin 和当前组件的各种数据,细分为四种策略:
替换型策略 - 同名的 props、methods、inject、computed 会被后来者替代
合并型策略 - data,通过 set 方法进行合并和重新赋值
队列型策略 - 生命周期钩子、watch。将函数存入一个数组,顺序执行
叠加型策略 - components、directives、filters,通过原型链层层叠加
插件
插件就是指对 Vue 的功能的增强或补充
- 什么是插件?如何编写一个插件
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVNode) {}
})
// 3. 注入组件选项
Vue.mixin({
created: function () {}
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (options) {
}
}
Vue.use(plugin, options)
- Vue.use做了什么
- 判断当前插件是否已经安装过,防止重复安装
- 处理参数,调用插件的 install 方法。第一个参数是 Vue 实例。
Vue.use = function (plugin, options) {
const installedPlugins = this._installedPlugins || (this._installedPlugins = [])
if (installedPlugins.indexOf(plugin) > -1) return this
// init plugin.install()
const args = Array.prototype.slice.call(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}