Config Provider 全局配置解析
- 1,介绍
- 2,使用
- 3,全局配置的实现
- 3.1,el-config-provider
- renderSlot
- 3.2,provideGlobalConfig
- 3.3,总结
官方文档:Config Provider 全局配置
1,介绍
在组件库的设计中,最常见的全局配置是 z-index
和 size
z-index
:统一管理弹出层(dialog, message, loading)等组件的层级。size
:统一管理表单元素大小。
这些都属于全局配置项,可以Config Provider
全局配置来实现。
2,使用
全局配置项有2种引入方式。但都是调用同一个全局配置函数 provideGlobalConfig
实现的,后面会详细介绍。
1,完整引入
import { createApp } from 'vue'import ElementPlus from 'element-plus'import App from './App.vue'const app = createApp(App)app.use(ElementPlus, { size: 'small', zIndex: 3000 })
相当于在element-plus
的install
方法中,调用了provideGlobalConfig()
const install = (app, options) => {// ...if (options) provideGlobalConfig(options, app, true)// ...}
2,按需引入
<template><el-config-provider :size="size" :z-index="zIndex"><app /></el-config-provider></template><script>import { defineComponent } from 'vue'import { ElConfigProvider } from 'element-plus'export default defineComponent({components: {ElConfigProvider,},setup() {return {zIndex: 3000,size: 'small',}},})</script>
- 相当于在
el-config-provider
组件中,调用了provideGlobalConfig()
- 如果将根组件作为
el-config-provider
的子组件传入,也相当于全局引入。
el-config-provider
组件,传入指定的配置项props
,在子组件中生效。
3,全局配置的实现
现在明确了:
- 完整引入通过
install
方法调用provideGlobalConfig()
, - 按需引入通过
el-config-provider
组件调用provideGlobalConfig()
,
install
方法没什么好说的,所以先介绍下el-config-provider
组件,再来详细介绍provideGlobalConfig()
。
3.1,el-config-provider
组件路径:
packages\components\config-provider\src\config-provider.ts
代码稍微做了简化。
import { defineComponent, renderSlot } from 'vue'import { provideGlobalConfig } from './hooks/use-global-config'const ConfigProvider = defineComponent({name: 'ElConfigProvider',// locale | size | zIndex | namespace | button | message 等props: configProviderProps,setup(props, { slots }) {const config = provideGlobalConfig(props)return () => renderSlot(slots, 'default', { config: config?.value })},})export default ConfigProvider
renderSlot
renderSlot()
和h('div', xxx)
一样,会返回 VNode,但参数不一致。
可以在
setup()
中返回渲染函数来创建组件。
export declare function renderSlot(slots: Slots, name: string, props?: Data, fallback?: () => VNodeArrayChildren, noSlotted?: boolean): VNode;
主要介绍前3个参数:
- slots:父组件传入的插槽内容。
- name:具名插槽的名称。
带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 slot 出口会隐式地命名为“default”
所以 renderSlot(slots, 'default', { config: config?.value })
定义的是默认插槽。
- props:传给父组件的 props。
结合以上,Config Provider
组件如果使用 template 定义,大致如下
<template><div><slot :config="config"></slot></div></template><script lang="ts" setup>const config = provideGlobalConfig(props)</script>
关于传给父组件的 props 的举例:
<!-- 子组件 --><div><slot :text="greetingMessage" :count="1"></slot></div><MyComponent v-slot="slotProps">{{ slotProps.text }} {{ slotProps.count }}</MyComponent>
3.2,provideGlobalConfig
先介绍依赖的变量和函数
1,定义全局配置对象globalConfig
// 全局配置对象const globalConfig = ref()// 全局配置依赖注入 provide/inject 的 keyconst configProviderContextKey = Symbol()
2,获取全局配置的 hook 函数
useGlobalConfig()
直接调用,获取全局配置useGlobalConfig('namespace', 'el')
传参调用,获取指定的全局配置
import { getCurrentInstance } from 'vue'function useGlobalConfig(key, defaultValue = undefined) {const config = getCurrentInstance() ? inject(configProviderContextKey, globalConfig) : globalConfigif (key) {return computed(() => config.value?.[key] ?? defaultValue)} else {return config}}
3,合并(新旧)全局配置
// 新值 b 会覆盖旧值 aconst mergeConfig = (a, b) => {const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])]const obj = {}for (const key of keys) {obj[key] = b[key] ?? a[key]}return obj}
4,provideGlobalConfig
有3个参数,是因为有2种调用方式。
- 按需引入,通过
el-config-provider
组件调用时,只会传入全局配置参数config
- 完整引入,通过 install 方法(作为插件)调用时,是应用层 Provide,所以还需要应用实例参数
app
,此时参数global = true
,会对全局配置对象globalConfig
赋值。
import { getCurrentInstance } from 'vue'const provideGlobalConfig = (config, app, global = false) => {// 在 setup() 或 中 getCurrentInstance() 才有值const inSetup = !!getCurrentInstance()const oldConfig = inSetup ? useGlobalConfig() : undefined// 应用层 Provide 需要使用 app.provideconst provideFn = app?.provide ?? (inSetup ? provide : undefined)if (!provideFn) {debugWarn('provideGlobalConfig', 'provideGlobalConfig() can only be used inside setup().')return}// 合并后的全局配置const context = computed(() => {const cfg = unref(config)if (!oldConfig?.value) return cfgreturn mergeConfig(oldConfig.value, cfg)})// 最核心的代码:provide 全局配置,这样才能在 useGlobalConfig() 中获取到全局配置provideFn(configProviderContextKey, context)// 下面这些都是 provide 指定的全局配置provideFn(localeContextKey, // Symbol('localeContextKey')computed(() => context.value.locale))provideFn(namespaceContextKey, // Symbol('namespaceContextKey')computed(() => context.value.namespace))provideFn(zIndexContextKey, // Symbol('zIndexContextKey')computed(() => context.value.zIndex))provideFn(SIZE_INJECTION_KEY, { // Symbol('size')size: computed(() => context.value.size || ''),})// install 方法中调用 || 初次赋值if (global || !globalConfig.value) {globalConfig.value = context.value}return context}
3.3,总结
- 在
provideGlobalConfig
中provide
的依赖,在使用的地方inject
即可。
举例
const useLocale = (localeOverrides) => {const locale = localeOverrides || inject(localeContextKey, ref())// ...}
provideGlobalConfig
最终返回的context
会作为el-config-provider
组件的默认插槽的 props 使用。
举例:
<template><el-config-provider :size="size" :z-index="zIndex" v-slot="{ config }"><div>{{ config.namespace }}</div><app /></el-config-provider></template>
以上。