V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jeremye
V2EX  ›  TypeScript

请教一个 TS 问题,不知能否实现

  •  
  •   jeremye · 14 天前 · 1208 次点击

    目前使用以下代码可以正常进行的类型推导,但每定义一个 item 都需要调用一次 defineItem,想要实现一个 defineConfig 方法,省略掉 defineItem,同时保留原先的类型推导。

    interface Options<T> {
      a: () => T;
      b: (p: T) => void;
    }
    
    function defineItem<T>(options: Options<T>) {
      return options;
    }
    
    const config = {
      item1: defineItem({
        a: () => ({ id: 1 }),
        b: (params) => {}, // params 类型是 { id: number }
      }),
      item2: defineItem({
        a: () => ({ name: '1' }),
        b: (params) => {}, // params 类型是 { name: string }
      }),
    };
    

    预期的效果

    const config = defineConfig({
      item1: {
        a: () => ({ id: 1 }),
        b: (params) => {}, // params 类型是 { id: number }
      },
      item2: {
        a: () => ({ name: '1' }),
        b: (params) => {}, // params 类型是 { name: string }
      },
    });
    
    第 1 条附言  ·  13 天前

    最终想要实现的效果如下,其中的 method1 可以动态,并且可以再定义 method2、method3...,c 的参数和返回值分别约束为 a 与 b 的返回值,defineModule 的返回值类型就保留原本的结构,如果这样设计不能实现的话,有没有更好的方式来设计这个 API 呢?

    type Module<T, K> = {
      method1: {
        a: () => T
        b: () => K
        c: (params: T) => K
      }
    }
    
    const module = defineModule({
      method1: {
        a: () => ({ id: 1 }),
        b: () => ({ id: 1, name: 'test' }),
        // c: (params: { id: number; }) => { id: number; name: string; }; }
        c: (params) => {}
      }
    })
    
    /*
    module: {
      method1: {
        a: () => {
            id: number;
        };
        b: () => {
            id: number;
            name: string;
        };
        c: (params: {
            id: number;
        }) => {
            id: number;
            name: string;
        };
      }
    }
    */
    
    function defineModule<T, K>(module: Module<T, K>) {
      return module
    }
    
    12 条回复    2024-12-05 10:07:34 +08:00
    bagel
        1
    bagel  
       13 天前
    Partial?
    cheerxl
        2
    cheerxl  
       13 天前
    ```typescript
    function defineConfig<T>(options: Record<string, Options<T>>) {
    return options;
    }

    const config = defineConfig<Record<string, any>>({
    item1: {
    a: () => ({ id: 1 }),
    b: (params) => {}, // params 类型是 { id: number }
    },
    item2: {
    a: () => ({ name: '1' }),
    b: (params) => {}, // params 类型是 { name: string }
    },
    });
    ```
    ltaoo1o
        3
    ltaoo1o  
       13 天前   ❤️ 1
    问了下 GPT ,稍微调整了下

    ```
    interface Options<T> {
    a: () => T;
    b: (p: T) => void;
    }

    type DefineConfig<T> = {
    [K in keyof T]: Options<T[K]>;
    };

    function defineConfig<T extends Record<string, any>>(config: DefineConfig<T>): T {
    return config;
    }

    // 使用 defineConfig 定义配置对象
    const config = defineConfig({
    item1: {
    a: () => ({ id: 1 }),
    b: (params) => {
    console.log(params.id); // params 类型是 { id: number }
    },
    },
    item2: {
    a: () => ({ name: '1' }),
    b: (params) => {
    console.log(params.name);
    // params 类型是 { name: string }
    },
    },
    });

    ```
    muben
        4
    muben  
       13 天前
    ```ts
    interface Options<T extends any> {
    a: () => T;
    b: (p: T) => void;
    }

    type ConfigType<T extends Record<string, Options<any>>> = {
    [K in keyof T]: {
    a: () => T[K]['a'];
    b: (p: ReturnType<T[K]['a']>) => void;
    };
    };

    function defineConfig<T extends Record<string, Options<any>>>(options: T): ConfigType<T> {
    return options;
    }

    const config = defineConfig({
    item1: {
    a: () => ({ id: 1 }),
    b: (params) => {}, // params 类型是 { id: number }
    },
    item2: {
    a: () => ({ name: '1' }),
    b: (params) => {}, // params 类型是 { name: string }
    },
    });

    config.item1.b({ id: 1 }); // ok
    config.item1.b({ name: '1' }); // error

    config.item2.b({ id: 1 }); // error
    config.item2.b({ name: '1' }); // ok
    ```
    jeremye
        5
    jeremye  
    OP
       13 天前
    @li1218040201 非常感谢,但是不太能理解 DefineConfig 中的 T[K] 传给 Options 后,T 为什么被推导为
    ```typescript
    {
    item1: {
    id: number;
    };
    item2: {
    name: string;
    };
    }
    ```
    jeremye
        6
    jeremye  
    OP
       13 天前
    @jeremye 问了 GPT 解释也是有点没懂..
    xzyDeathGun
        7
    xzyDeathGun  
       13 天前
    @jeremye 因为是错的,4 楼那个是对的
    ltaoo1o
        8
    ltaoo1o  
       13 天前
    @jeremye 我有自己的理解,但解释不了,你的需求和小程序 Page({}) 传参,参数有各种提示类似,看看 miniprogram-type 包。也可以找找 vite 这种框架,有 defineConfig 方法,参考它们的类型声明。
    jeremye
        9
    jeremye  
    OP
       13 天前
    @xzyDeathGun 4 楼的那个返回值类型是正确的,但是传入 defineConfig 的参数不是正确的,在定义 config 时,params 的类型是 any ,可能是我的表达不够准确,初衷是帮助开发者在定义 config 时获得更友好的类型提示。
    DOLLOR
        10
    DOLLOR  
       13 天前
    function defineConfig<T>(config: {
    [Property in keyof T]: {
    a: () => T[Property]
    b: (p: T[Property]) => void
    }
    }) {
    return config
    }

    @jeremye 只能说 ts 过于牛逼,令人恐惧,居然能从 config 反推 T 的结构🤣
    jeremye
        11
    jeremye  
    OP
       13 天前
    @li1218040201 #8 之前只关注到了参数的类型,现在发现返回值类型也是 `{ item1: { id: number; }; item2: { name: string; }; }`, 似乎没办法保留参数原本的类型
    ltaoo1o
        12
    ltaoo1o  
       13 天前
    @jeremye 10 楼的方案我试了没问题的,你看下?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5353 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 08:25 · PVG 16:25 · LAX 00:25 · JFK 03:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.