如何骚着用 Vue3 的 provider/inject (干货)
我们做一个父子组件数据传递,一般会使用 props,当我们需要多层级向下子组件传递使用 `provider`, `inject` 。而接下来本文提供一种比较 hack 的方式去使用 provider/inject。

0x00
一般而言,我们做一个父子组件数据传递,使用 props,当我们需要多层级向下子组件传递使用 provider
, inject
。摘官方文档:
父组件可以作为其所有子组件的依赖项提供程序,而不管组件层次结构有多深。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这个数据。
0x01
我们可以得知,利用 provider
, inject
可以在 Vue3 中可无视多子层级直接传递需要的数据。
而一般的用法可以如下:
import { createApp, defineComponent, provider, inject, reactive, readonly, toRefs } from 'vue';
// Provider 包装组件
const MyConfigProvider = defineComponent({
name: 'MyConfigProvider',
props: ['prefixCls', 'title', 'i18n'],
setup (props, { slots }: SetupContext) {
const { prefixCls, title, i18n } = toRefs(props);
const context = reactive({
prefixCls,
title,
i18n
});
provide('myConfig', readonly(context));
return () => slots.default?.();
}
});
// 测试用子组件
const ChildComp = defineComponent({
name: 'ChildComp',
setup () {
const myConfig = inject('myConfig', {});
return () => (
<>
<p>{myConfig.prefixCls}</p>
<p>{myConfig.title}</p>
</>
)
}
});
// App.vue
const App = defineComponent({
name: 'App',
setup () {
const state = reactive({
prefixCls: 'myui',
title: 'MyApp',
i18n: (key: string) => key
});
return () => (
<div id="#app">
<MyConfigProvider {...state}>
<ChildComp />
</MyConfigProvider>
</div>
)
}
});
const app = createApp(App);
app.mount('#app');
从上面的用法,可以看出,如果需要默认参数,或者注入 KEY 等,我们需要每次频繁的写 inject(Key, defauleValue);
,并且还需要创建一个专门用于向下传递的 provider
包装组件来进行参数传递。
0x02
为了减少这方面的代码,可以抽离出一个专门用来创建 上下文属性传递的 provider
, inject
套娃封装。
如下:
// context.ts
// 注入类型
export type ContextType<T> = any;
// 返回类型
export type CreateContext<T> = {
UnwrapRef<T> | T,
DefineComponent<{}, () => VNode | VNode[] | undefined, any>,
}
// 创建 Provider 和响应式数据绑定方法 (provider)
export const createContext = <T>(
context: ContextType<T>,
contextInjectKey: InjectionKey<ContextType<T>> = Symbol(),
): CreateContext<T> => {
const state = reactive<ContextType<T>>({
...toRaw(context),
});
const ContextProvider = defineComponent({
name: 'ContextProvider',
inheritAttrs: false,
setup(props, { slots }: SetupContext) {
// readonly 是为了防止被子组件修改状态
provide(contextInjectKey, readonly(state));
return () => slots.default?.();
},
});
return [
state,
ContextProvider,
];
};
// 使用 Provider 传递的属性 方法(inject)
export const useContext = <T>(
contextInjectKey: InjectionKey<ContextType<T>> = Symbol(),
defaultValue?: ContextType<T>,
): T => {
return readonly(inject(contextInjectKey, defaultValue || ({} as T)));
};
上面这样的"套娃封装",既完成了一个简单的对 provider
, inject
封装,现在可以利用 createContext()
来创建一个 Provider 和返回相应的响应数据。
<template>
<my-context-provider>
<your-custom-component />
<your-custom-component2 />
<your-custom-component3 />
</my-context-provider>
</template>
<script lang="ts">
// 忽略 import
interface MyContextProps {
param1: string;
param2: boolean;
someData?: string[];
}
// 现在可以在需要进行传递属性的组件外进行一层 `MyContextProvider` 的包装,如:
const [ state, MyContextProvider ] = createContext<MyContextProps>({
param1: 'abc',
param2: false,
someData: ['a', 'b', 'c', 'd']
});
export default defineComponent({
components: {
MyContextProvider,
},
setup() {
state.param1 = 'aaa';
return {};
}
});
</script>
现在这样,能做到 state
改变,对该 provider
下面的所有使用了 useContext
组件的内部注入属性均发生改变。
但目前状况,是同样需要填写 inject key
来进行注入的,所有又衍生的再次套娃封装
import { InjectionKey } from 'vue';
import { createContext, useContext } from './hooks/context';
export interface RouteContextProps {
breadcrumb?: any;
menuData?: any[];
isMobile?: boolean;
prefixCls?: string;
collapsed?: boolean;
hasSideMenu?: boolean;
hasHeader?: boolean;
sideWidth?: number;
hasFooterToolbar?: boolean;
hasFooter?: boolean;
setHasFooterToolbar?: (bool: boolean) => void;
}
// 定义注入 key
const routeContextInjectKey: InjectionKey<RouteContextProps> = Symbol();
// 创建注入组件和响应式数据
export const createRouteContext = (context: RouteContextProps) =>
createContext<RouteContextProps>(context, routeContextInjectKey);
// 子组件使用响应式数据
export const useRouteContext = () => useContext<RouteContextProps>(routeContextInjectKey);
上述详细源码和封装代码,可以从下面链接获取和查看
- github.com/vueComponent/pro-layout/hooks/context
- github.com/vueComponent/pro-layout/examples
- github.com/vueComponent/pro-layout/RouteContext
备注:以上的方法命名参考
React Hooks
本文同时发布在 掘金@Sendya
本文原创(@Sendya)转载请注明出处和原作者,谢谢。