相关推荐recommended
element-plus 架构 - Config Provider 全局配置
作者:mmseoamin日期:2024-02-03

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,按需引入

          
          
          1. 相当于在el-config-provider组件中,调用了provideGlobalConfig()
          2. 如果将根组件作为el-config-provider的子组件传入,也相当于全局引入。

          el-config-provider组件,传入指定的配置项props,在子组件中生效。

          3,全局配置的实现

          现在明确了:

          1. 完整引入通过 install 方法调用provideGlobalConfig(),
          2. 按需引入通过 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个参数:

          1. slots:父组件传入的插槽内容。
          2. name:具名插槽的名称。

          带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 slot 出口会隐式地命名为“default”

          所以 renderSlot(slots, 'default', { config: config?.value }) 定义的是默认插槽。

          1. props:传给父组件的 props。

          结合以上,Config Provider 组件如果使用 template 定义,大致如下

          
          

          关于传给父组件的 props 的举例:

          
          
          {{ slotProps.text }} {{ slotProps.count }}

          3.2,provideGlobalConfig

          先介绍依赖的变量和函数

          1,定义全局配置对象globalConfig

          // 全局配置对象
          const globalConfig = ref()
          // 全局配置依赖注入 provide/inject 的 key
          const configProviderContextKey = Symbol()
          

          2,获取全局配置的 hook 函数

          • useGlobalConfig()直接调用,获取全局配置
          • useGlobalConfig('namespace', 'el')传参调用,获取指定的全局配置
            import { getCurrentInstance } from 'vue'
            function useGlobalConfig(key, defaultValue = undefined) {
              const config = getCurrentInstance() ? inject(configProviderContextKey, globalConfig) : globalConfig
              if (key) {
                return computed(() => config.value?.[key] ?? defaultValue)
              } else {
                return config
              }
            }
            

            3,合并(新旧)全局配置

            // 新值 b 会覆盖旧值 a
            const 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() 或