ConfigProvider
ConfigProvider 用于全局配置 Vant 组件,提供深色模式、主题定制等能力。
实现原理
利用 css 变量实现。
vue
<template>
<!-- 变成绿色背景 -->
<div style="--van-button-primary-background: #07c160;">
<van-button type="primary">主要按钮</van-button>
</div>
</template>
组件注册
ts
import { createApp } from 'vue';
import { ConfigProvider } from 'vant';
const app = createApp();
app.use(ConfigProvider);
使用
vue
<template>
<!-- 深色模式 -->
<van-config-provider theme="dark">...</van-config-provider>
<!-- 覆盖 css 变量 -->
<van-config-provider :theme-vars="themeVars">...</van-config-provider>
<!-- 覆盖不同模式下的 css 变量 -->
<van-config-provider
:theme-vars="themeVars"
:theme-vars-dark="themeVarsDark"
:theme-vars-light="themeVarsLight"
>
...
</van-config-provider>
</template>
<script setup>
import { reactive } from 'vue'
const themeVars = reactive({ buttonPrimaryBackground: 'red' });
const themeVarsDark = reactive({ buttonPrimaryBackground: 'blue' });
const themeVarsLight = reactive({ buttonPrimaryBackground: 'green' });
</script>
组件源码
tsx
import {
watch,
provide,
computed,
watchEffect,
onActivated,
onDeactivated,
onBeforeUnmount,
defineComponent,
type PropType,
type InjectionKey,
type CSSProperties,
type ExtractPropTypes,
} from 'vue';
import {
extend,
inBrowser,
kebabCase,
makeStringProp,
createNamespace,
type Numeric,
} from '../utils';
import { setGlobalZIndex } from '../composables/use-global-z-index';
const [name, bem] = createNamespace('config-provider');
export type ConfigProviderTheme = 'light' | 'dark';
export type ConfigProviderProvide = {
iconPrefix?: string;
};
export const CONFIG_PROVIDER_KEY: InjectionKey<ConfigProviderProvide> =
Symbol(name);
export type ThemeVars = PropType<Record<string, Numeric>>;
export const configProviderProps = {
tag: makeStringProp<keyof HTMLElementTagNameMap>('div'),
theme: makeStringProp<ConfigProviderTheme>('light'),
zIndex: Number,
themeVars: Object as ThemeVars,
themeVarsDark: Object as ThemeVars,
themeVarsLight: Object as ThemeVars,
iconPrefix: String,
};
export type ConfigProviderProps = ExtractPropTypes<typeof configProviderProps>;
// 将主题变量对象转换成 css 变量对象
function mapThemeVarsToCSSVars(themeVars: Record<string, Numeric>) {
const cssVars: Record<string, Numeric> = {};
Object.keys(themeVars).forEach((key) => {
cssVars[`--van-${kebabCase(key)}`] = themeVars[key];
});
// { buttonPrimaryBackground: 'red' } --> { --van-button-primary-background: 'red' }
return cssVars;
}
export default defineComponent({
name,
props: configProviderProps,
setup(props, { slots }) {
const style = computed<CSSProperties | undefined>(() =>
mapThemeVarsToCSSVars(
extend(
{},
props.themeVars,
props.theme === 'dark' ? props.themeVarsDark : props.themeVarsLight
)
)
);
if (inBrowser) {
// 添加 vant 主题类名
const addTheme = () => {
document.documentElement.classList.add(`van-theme-${props.theme}`);
};
// 移除 vant 主题类名
const removeTheme = (theme = props.theme) => {
document.documentElement.classList.remove(`van-theme-${theme}`);
};
// 监听到主题变化,移除老主题 class,添加新主题 class
watch(
() => props.theme,
(newVal, oldVal) => {
if (oldVal) {
removeTheme(oldVal);
}
addTheme();
},
{ immediate: true }
);
onActivated(addTheme);
onDeactivated(removeTheme);
onBeforeUnmount(removeTheme);
}
// 将配置注入后代组件中,目前就 icon 组件中使用到 icon-prefix 属性配置
provide(CONFIG_PROVIDER_KEY, props);
watchEffect(() => {
if (props.zIndex !== undefined) {
// 设置所有弹窗类组件的 z-index
setGlobalZIndex(props.zIndex);
}
});
return () => (
<props.tag class={bem()} style={style.value}>
{slots.default?.()}
</props.tag>
);
},
});