我们知道在 Vue 中插槽是通过模板传递的,那如果所有用到这个组件的地方都需要某个插槽时,可能就不得不进行二次封装了,我们希望插槽能做到像属性 props 一样全局传递。就像这样:
- Vue 3
app.use(YourComponent, {
// 全局 Slot
'#left-footer': () => h('Fragment', undefined, 'Global Slot'),
// 全局 Scoped Slot
'#default': ({ option }) => h('Fragment', undefined, `${option.label} (From Global Scoped Slot)`),
})
- Vue 2
Vue.use(YourComponent, {
// 全局 Slot
'#left-footer': () => ({ render: h => h('span', undefined, 'Global Slot') }),
// 全局 Scoped Slot
'#default': ({ option }) => ({ render: h => h('span', undefined, `${option.label} (From Global Scoped Slot)`) }),
})
全局传参
在 Vue 中,参数有 props/attrs/listeners/hooks/slots 等多种形式,实现全局传递 props/attrs 是较为常见的,Element / Element Plus 就是案例,全局 listeners/hooks 使用 this.$listeners
+ 劫持 this.$emits
也可以实现:
- Vue 3
app.use(YourComponent, {
// 全局 Prop
'title': 'Global Title',
// 全局 Attr
'data': [
{ key: 1, label: 'Global Option 1' },
{ key: 2, label: 'Global Option 2' },
],
// 全局 Listener
'@leftCheckChange': function () {
console.log('Global LeftCheckChange')
},
// 全局 Hook
'@vnodeMounted': function () {
console.log('Global Mounted')
},
})
- Vue 2
Vue.use(YourComponent, {
// 全局 Prop
'title': 'Global Title',
// 全局 Attr
'data': [
{ key: 1, label: 'Global Option 1' },
{ key: 2, label: 'Global Option 2' },
],
// 全局 Listener
'@left-check-change': function () {
console.log('Global LeftCheckChange')
},
// 全局 Hook
'@hook:mounted': function () {
console.log('Global Mounted')
},
})
全局配置与局部配置如何进行合并?
原始类型
直接 ??
一个短路可以简单搞定,不过严谨地来说,只有 undefined
才能视为没有传参,null
应该被理解为传了但为空,可以使用 vue-global-config 提供的 conclude
来处理这个问题。
对象类型
有三种情况,可以是局部配置覆盖全局配置,可以是浅合并,就像现在 Vue 3 的 mixins 合并策略,也可以是深合并,就像 Vue 2 的 mixins 合并策略,而这些 conclude
都支持。
函数类型
可以是局部配置覆盖全局配置,可以是两者都触发,conclude
提供了函数融合器来解决这个问题,高度灵活。
如何在全局监听器/全局钩子中访问 this
?
组件在绑定全局监听器/全局钩子前逐一给它们 .bind(this)
。
全局插槽
全局插槽需要通过编程式的方式进行传递,插槽是什么,是一个 HTML 片段,那我们要怎样将一个 HTML 片段以编程方式全局传递给一个组件呢?
答案:使用动态组件 <component :is="xxx">
,动态组件不是只支持已经注册了的组件名称吗?非也,其实 is
支持的格式很多。
方式一:渲染函数 h
/createVNode
仅限 Vue 3
app.use(YourComponent, {
'#left-footer': () => h('Fragment', undefined, 'Global Slot'),
})
代码简洁,如果你使用的是 Vue 3,推荐此方式。
方式二:组件定义 + 渲染函数
支持 Vue 2.6/2.7/3
app.use(YourComponent, {
'#left-footer': () => ({ render: h => h('span', undefined, 'Global Slot') }),
})
如果你使用的是 Vue 2,推荐此方式。
方式三:组件定义 + 模板
支持 Vue 2.6/2.7/3
app.use(YourComponent, {
'#left-footer': () => ({ template: '...' }),
})
方式四:组件构造器
仅限 Vue 2.6/2.7
Vue.use(YourComponent, {
'#left-footer': () => Vue.extend(...),
})
由于 Vue.extend
在 Vue 3 中已被废弃,故不推荐此方式。
方式五:导入的 SFC
支持 Vue 2.6/2.7/3
import SomeComponent from './SomeComponent.vue'
app.use(YourComponent, {
'#left-footer': () => SomeComponent,
})
适用于插槽较为复杂的情况。
方式六:局部或全局注册过的组件名称
支持 Vue 2.6/2.7/3
app.use(YourComponent, {
'#left-footer': () => 'SomeComponent',
})
适用于插槽是某个已经注册过的组件的情况。
全局作用域插槽
通过给函数传参实现,这也是为什么前面的普通插槽也是函数的形式,只是为了保持一致的格式。
app.use(YourComponent, {
'#default': ({ option }) => h('Fragment', undefined, `${option.label} (From Global Scoped Slot)`),
})
如何保证局部插槽的优先级高于全局插槽?
这里有一个坑,在 Vue 3 中,通过书写顺序就能解决这个问题,但在 Vue 2 中不行。
Vue 3
在 Vue 3 中,对于同名插槽,书写在后面的优先级更高,所以我们将局部插槽书写在后面即可。
Vue 2
在 Vue 2 中,需要先将全局插槽与局部插槽通过 conclude
进行融合,对于同名插槽,仅留下优先级高的局部插槽,在渲染时判断插槽是来自于全局还是局部,选择不同的标签进行渲染。