wujingjing
2024-11-18 8703f7ddda1cbdbee1cefc4c8f9a31ea97272494
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
 * @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];
};