插槽-Slot
- 默认插槽:子组件用来slot标签确定渲染的位置,标签内可以正常写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}
})
- 具名插槽:子组件在使用slot标签时定义了slot标签的name属性,在父组件使用的时候就可以根据插槽的名字往具体插槽插入内容
Vue.component('button-counter', {
template:
'<div>
<slot>我是默认内容</slot>
<slot name='center'>我是默认中间内容</slot>
<slot name='buttom'>我是默认底部内容</slot>
</div>'
})
new Vue({
el: '#app',
template: '<button-counter><span slot='bottom'>我是slot传?的底部内容</span></button-counter>',
components:{buttonCounter}
})
- 插槽的实现原理:
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 renderSlot (name, fallback, props, bindObject) {
var scopedSlotFn = this.$scopedSlots[name];
var nodes;
nodes = scopedSlotFn(props) || fallback;
return nodes;
}
?scopedSlots其实就是递归解析各个节点, 获取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;
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot;
}
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
var name = data.slot;
var slot = (slots[name] || (slots[name] = []));
if (child.tag === 'template') {
slot.push.apply(slot, child.children || []);
} else {
slot.push(child);
}
} else {
(slots.default || (slots.default = [])).push(child);
}
}
for (var name$1 in slots) {
if (slots[name$1].every(isWhitespace)) {
delete slots[name$1];
}
}
return slots
}
附Vue插槽实现原理
混入-Mixin
本质其实就是?个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等 Tips
- 当组件存在与mixin对象相同的数据的时候,进?递归合并的时候组件的数据会覆盖mixin的数据
- 如果相同数据为?命周期钩?的时候,会合并成?个数组,先执?mixin的钩?,再执?组件的钩?
mixin的实现原理
- 优先递归处理 mixins
- 先遍历合并parent 中的key,调?mergeField?法进?合并,然后保存在变量options
- 再遍历 child,合并补上 parent 中没有的key,调?mergeField?法进?合并,保存在变量
options - 通过 mergeField 函数进?了合并
export function mergeOptions(parent: Object, child: Object, vm?: Component): Object {
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) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(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会被后来者代替
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
if (!parentVal) return childVal
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
return ret
}
- 合并型策略 - data, 通过set?法进?合并和重新赋值
strats.data = function (parentVal, childVal, vm) {
return mergeDataOrFn(
parentVal, childVal, vm
)
};
function mergeDataOrFn(parentVal, childVal, vm) {
return function mergedInstanceDataFn() {
var childData = childVal.call(vm, vm)
var parentData = parentVal.call(vm, vm)
if (childData) {
return mergeData(childData, parentData)
} else {
return parentData
}
}
}
function mergeData(to, from) {
if (!from) return to
var key, toVal, fromVal;
var keys = Object.keys(from);
for (var i = 0; i < keys.length; i++) {
key = keys[i];
toVal = to[key];
fromVal = from[key];
if (!to.hasOwnProperty(key)) {
set(to, key, fromVal);
}
else if (typeof toVal == "object" && typeof fromVal == "object") {
mergeData(toVal, fromVal);
}
}
return to
}
- 队列型策略 - ?命周期函数和watch,原理是将函数存??个数组,然后正序遍历依次执?
function mergeHook(
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
- 叠加型策略 - component、directives、filters,通过原型链进?层层的叠加
strats.components=
strats.directives=
strats.filters = function mergeAssets(
parentVal, childVal, vm, key
) {
var res = Object.create(parentVal || null);
if (childVal) {
for (var key in childVal) {
res[key] = childVal[key];
}
}
return res
}
插件-Plugin
插件就是指对Vue的功能的增强或补充
- 什么是插件? 如何编写?个插件?
MyPlugin.install = function (Vue, options) {
Vue.myGlobalMethod = function () {
}
Vue.directive('my-directive', {
bind(el, binding, vnode, oldVnode) {
}
...
})
Vue.mixin({
created: function () {
}
...
})
Vue.prototype.$myMethod = function (methodOptions) {
}
}
Vue.use(plugin, options);
- Vue.use做了什么?
判断当前插件是否已经安装过, 防?重复安装 处理参数, 调?插件的install?法, 第?个参数是Vue实例
import { toArray } from '../util/index'
export function initUse(Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins
= []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(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
}
}
``
|