element-plus 架构 – Config Provider 全局配置

  • Post author:
  • Post category:其他



官方文档: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>
  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 定义,大致如下

<template>
  <div>
	<slot :config="config"></slot>
  </div>
</template>

<script lang="ts" setup>
const config = provideGlobalConfig(props)
</script>

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

<!-- <MyComponent> 子组件 -->
<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 的 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() 或 <script setup> 中 getCurrentInstance() 才有值
  const inSetup = !!getCurrentInstance()
  const oldConfig = inSetup ? useGlobalConfig() : undefined
  // 应用层 Provide 需要使用 app.provide
  const 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 cfg
    return 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,总结



  1. provideGlobalConfig



    provide

    的依赖,在使用的地方

    inject

    即可。

举例

const useLocale = (localeOverrides) => {
  const locale = localeOverrides || inject(localeContextKey, ref())
  // ...
}

  1. 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>

以上。



版权声明:本文为qq_40147756原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。