/**
|
* @fileoverview 组件内部复用一个代码模板,避免过于细粒度定义组件
|
* 使用方法见{@link https://mp.weixin.qq.com/s/N9Myz7iCM90Eah-4p3ksgg}
|
* @author wujingjing <gersonwu@qq.com>
|
*/
|
|
import { camelCase } from 'lodash-es';
|
import { defineComponent, shallowRef } from 'vue';
|
|
import type { DefineComponent, Slot } from 'vue';
|
|
// 将横线命名转大小驼峰
|
function keysToCamelKebabCase(obj: Record<string, any>) {
|
const newObj: typeof obj = {};
|
for (const key in obj) newObj[camelCase(key)] = obj[key];
|
return newObj;
|
}
|
|
export type DefineTemplateComponent<
|
Bindings extends object,
|
Slots extends Record<string, Slot | undefined>
|
> = DefineComponent<object> & {
|
new (): { $slots: { default(_: Bindings & { $slots: Slots }): any } };
|
};
|
|
export type ReuseTemplateComponent<
|
Bindings extends object,
|
Slots extends Record<string, Slot | undefined>
|
> = DefineComponent<Bindings> & {
|
new (): { $slots: Slots };
|
};
|
|
export type ReusableTemplatePair<Bindings extends object, Slots extends Record<string, Slot | undefined>> = [
|
DefineTemplateComponent<Bindings, Slots>,
|
ReuseTemplateComponent<Bindings, Slots>
|
];
|
|
export const useTemplate = <
|
Bindings extends object,
|
Slots extends Record<string, Slot | undefined> = Record<string, Slot | undefined>
|
>(): ReusableTemplatePair<Bindings, Slots> => {
|
const render = shallowRef<Slot | undefined>();
|
|
const define = defineComponent({
|
setup(_, { slots }) {
|
return () => {
|
// 将复用模板的渲染函数内容保存起来
|
render.value = slots.default;
|
};
|
},
|
}) as DefineTemplateComponent<Bindings, Slots>;
|
|
const reuse = defineComponent({
|
setup(_, { attrs, slots }) {
|
return () => {
|
// 还没定义复用模板,则抛出错误
|
if (!render.value) {
|
throw new Error('复用模板未定义!');
|
}
|
// 执行渲染函数,传入 attrs、slots
|
const vNode = render.value({ ...keysToCamelKebabCase(attrs), $slots: slots });
|
return vNode.length === 1 ? vNode[0] : vNode;
|
};
|
},
|
}) as ReuseTemplateComponent<Bindings, Slots>;
|
|
return [define, reuse];
|
};
|