想请教下 TypeScript 里面几个泛型方法的写法 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
eraserking
V2EX    TypeScript

想请教下 TypeScript 里面几个泛型方法的写法

  •  
  •   eraserking 2022-08-10 14:29:12 +08:00 2604 次点击
    这是一个创建于 1244 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题有点长,不过主要是有例子

    比如说我有这样的一个 interface

    interface MyInterface { propertyA: string | null; propertyB: number | null; properyC: SomeOtherInterface | null; propertyD: AnotherOtherInterface | null; } interface SomeOtherInterface { propertyX: string | null; propertyY: number | null; } interface AnotherOtherInterface { propertyM: string | null; propertyN: number | null; } 

    然后我需要一个方法去修改这个 MyInterface 的一个对象上的属性(假如说返回一个新对象而不是原地修改),那么我可以写出这样的一个泛型方法:

    const updateProperty = < TKey extends keyof MyInterface, TValue extends MyInterface[TKey] >( originalObject: MyInterface, propertyName: TKey, propertyValue: TValue ): MyInterface => { // ... some internal logic return { ...originalObject, [propertyName]: propertyValue }; }; 

    到这里也没有问题,调用起来也很简单

    const new1 = updateProperty(original, "propertyA", "some string"); 

    第一个问题是,如果再写一个方法,一次性需要修改不确定数量的多个属性,这个泛型定义怎么写? 我想到的方法是,写个数组传

    const updateProperties = < TKey extends keyof MyInterface, TValue extends MyInterface[TKey] >( originalObject: MyInterface, propertiesToUpdate: { propertyName: TKey; propertyValue: TValue }[] ): MyInterface => { // ... some internal logic const updatedObject = { ...originalObject }; propertiesToUpdate.forEach( (p) => (updatedObject[p.propertyName] = p.propertyValue) ); return updatedObject; }; 

    设想的调用方法是

    const new2 = updateProperties(original, [ { propertyName: "propertyA", propertyValue: "some string", }, { propertyName: "propertyB", propertyValue: 10, }, ]); 

    但是实际上是不可以的,因为在修改多个不同类型的属性的时候,TKey 就是联合类型了,TValue 也会变成相对于 TKey 的联合类型,导致无法赋值回去 我想的是在 propertyName 为 A 的时候,propertyValue 只能为 string | null ,同时对于 B ,只能接收 number | null ,数组中各个元素是相互独立的 那我这个方法应该如何修改?

    第二个问题是,如果我想写一个方法去修改 propertyC -> propertyX / Y, propertyD -> propertyM/N ,这个泛型方法怎么写 期望的调用方法是

    updateSubProperty("propertyC", "propertyX", "some string"); updateSubProperty("propertyD", "propertyY", 20); 

    其中第一个参数限定为像 propertyC/D 这样的“对象”而不是 string number 这样的基础类型(描述的可能不准确),第二个第三个参数是限定为第一个参数确定下来的类型的属性和值 简单的想法如下

    const updateSubProperty = < TTop extends keyof MyInterface, // Should be restricted to some "object" only properties, but how? TSub extends keyof MyInterface[TTop], TValue extends keyof MyInterface[TTop][TSub] >( originalObject: MyInterface, topProperty: TTop, subProperty: TSub, value: TValue ) => { // ... some internal logic const originalSubObject = originalObject[topProperty] ?? ({} as TTop); // It's acceptable to leave some properties missing, no worry const newSubObject = { ...originalSubObject, [subProperty]: value }; // ERROR: Spread types may only be created from object types.ts(2698) return { ...originalObject, [topProperty]: newSubObject }; }; 

    然后理所当然是报了错,Spread types may only be created from object types.ts(2698) 原因我也知道,TTop 那边应该限制为 object-only ,像上面那样写的话依然可以给第一个参数传 propertyA ,这不是我想要的。那么这边的这个泛型约束应该如何定义?

    6 条回复    2022-08-12 11:15:01 +08:00
    ifdef
        1
    ifdef  
       2022-08-10 14:38:48 +08:00
    Partial<MyInterface>
    exonuclease
        2
    exonuclease  
       2022-08-10 16:26:30 +08:00
    const updateProperties = (
    originalObject: MyInterface,
    newProperties: Partial<MyInterface>
    ): MyInterface => {
    return { ...originalObject,...newProperties };
    };
    YuJianrong
        3
    YuJianrong  
       2022-08-12 07:51:10 +08:00
    第一个你需要类似
    YuJianrong
        4
    YuJianrong  
       2022-08-12 07:53:55 +08:00
    第一个你需要类似这样的 helper 类型:
    type Property2<P> = P extends keyof MyInterface ? {propertyName: P; propertyValue: MyInterface[P]}: never;

    详见 https://www.typescriptlang.org/play?ssl=33&ssc=107&pln=33&pc=1#code/JYOwLgpgTgZghgYwgAgLIE8CS5rycgbwChlkAHKAezOjHQEEAuZAZzClAHNkAfZEAK4AbIQG4S5KjSh0AQs0EBbAEbRe-YWIkVqtdAGFmAZUqKIAeTAALaNkixEKPoJHjSO6XQAizeiErW0JY2UHa4juouWgC+RESg9ngoJmbBtjgO+MTuUnoAGsxsHCDczppukroy6ACaCgIqamWuRLHxGUnIfgEhaaEdEdmVnuiohexckeXaudUAcvWNUFMtbQD0a8gIlCBsyAJkACZwkAAKs3TIALzIADxEG6TIACoA0hDoyBAAHpAghyxkABrD6UGBoLADJAAGgem1IzwAanAhAIUD8-gCIWFMhAANpvD4AXThyAAfAAKUmkSgcTigFHmZQAKwgCDAzAwOKSsMeOSqdDmcDMzEJ6F58OGemRqIgoplaNJAEpOZDEhErmTCNTkI8AHQG1imFAJaAgFHIISUekIHVQCBgARQECEZAGvW04D081CJms9nQ5B4jx6IVmInMEPVBUoaLiR5xuKPba7MDIABuKIAjKruRrCNFkHBAXAQOh45sU3sQBAAO5Z677I4nCDnAXoCmZoRZwMAIijdHovb7LGNrAmJV7SvERDoNGQbZGACZbqctTdTl9fhB-oCQegwdioSgAPyEAfoMNyheiKXRlFo3PHvGnInRBQQdPQGcwAQgdnADsTbHGcFzABALBLhSnreoyLJshyR7qjCd5gOBLDMIuegrvuh5cseZJ4kSSoEG0VZpl2S5PshKA3KRRYlmW4jkfwdZLo2Bwga2YEQVBlGBniEhDPyIxXsw-YXAww4SCJ0oPtevajmY47FJw0mkNEsKkMJqGXsKCkXrI6lPBeMbMFmAAMWnIJpQkybpYnIBJ7b6MZsn3rKzAEBeBS9lmw66XUyBZrZGmwsRM5EEAA


    第二个更复杂点,我的做法是这样的 helper 类型:
    type NonNull<T> = T extends null ? never : T;
    type IsSubjectObjectKey<K extends keyof MyInterface> = NonNull<MyInterface[K]> extends object ? K : never;
    type GetSubjectObjectKey<K extends keyof MyInterface> = K extends IsSubjectObjectKey<K> ? K : never

    const updateSubProperty = <
    TTop extends GetSubjectObjectKey<keyof MyInterface>, // Should be restricted to some "object" only properties, but how?
    TSub extends keyof (NonNull<MyInterface[TTop]>),
    TValue extends NonNull<MyInterface[TTop]>[TSub]
    >(

    注意你的类型中含|null 需要去掉。

    详见 https://www.typescriptlang.org/play?ssl=26&ssc=3&pln=18&pc=1#code/JYOwLgpgTgZghgYwgAgLIE8CS5rycgbwChlkAHKAezOjHQEEAuZAZzClAHNkAfZEAK4AbIQG4S5KjSh0AQs0EBbAEbRe-YWIkVqtdAGFmAZUqKIAeTAALaNkixEKPoJHjSO6XQAizeiErW0JY2UHa4juouWgC+RESg9ngoJmbBtjgO+MTuUnoAGsxsHCDczppukroy6ACaCgIqamWuRLHxGUnIfgEhaaEdEdmVnuiohexckeXaudUAcvWNUFMtbXQ0yHOUIHOaADwAKgB8yAC8yAfIEAAekCAAJiwaIsgA-PwQAG5qzAfi6yhMCwjAJlAArCAIMDmcGQsAAaQg6D28KutwgDyeAGskZQYGgsAMkCdzlsdvsMGFMhAANrwgC6Jxud0eyEosKhb2QqIUX2g-3QGwA4hAwCCOdCJYjkajmRjWTj0HiCVSkiTuWiWU8geKIVCYXqEUiUSd3jyPt8oHEENs2MgBGR7nBIOKAAqzOhnZB7CQHA7UTXyp4isWgw0GuHSvaK5WUokQI4AGmQAHoU8gjFZKMJ7shVMgoBAisAoRBc2BKKxTCgAETsw01tkgIToYa0YBF5PKARgZBZgDur194sDmOQMfxAAoybsRHs44lHDS-dRGQBKRO+gBqcCEAhQcrHM4phMXSGX-rIjOX4vpRCOk4klA4nFAu4jUOYC-CSE3pArZDulUdC-Jef6sKCQEjL84rgZ8u77r8O57hARBrmcJzENE4hEDaIB2vBQhfqeP4oOcBDRMgcBPHAIDoOIDpOi6kEeugk6EcmNYeHo+g1px3HVHkfHIDWLDVqwEwlDWa4MY6zoQG6rHsbu-GsbxqnAbUwkAEwAAwyUQjHyYpmnKUIGkjF4wlcaxqDCQA5Hp9kyUAA
    YuJianrong
        5
    YuJianrong  
       2022-08-12 07:54:56 +08:00
    范型函数里面的报错一般都不重要,就不要管了,any 吧……
    eraserking
        6
    eraserking  
    OP
       2022-08-12 11:15:01 +08:00
    @ifdef
    @exonuclease
    确实 Partial 是可的,只是我的例子里还有些简化的地方,可能单独的 Partial 不能满足,我自己再先想想,谢谢

    @YuJianrong 对于问题一,这个看起来更好一点,我其实在那个数组里还有些别的属性,这样的话我也可以加进来了
    对于问题二,我得到了点启发,用了两个 helper type 去限定类型,然后用 NonNullable 再限定一下,这样的话就可以不用去掉 null
    参见:
    https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgLIE8CS5rycgbwChlkAHKAezOjHQEEAuZAZzClAHNkAfZEAK4AbIQG4S5KjSh0AQs0EBbAEbRe-YWIkVqtdAGFmAZUqKIAeTAALaNkixEKPoJHjSO6XQAizeiErW0JY2UHa4juouWgC+RESg9ngoJmbBtjgO+MTuUnoAGsxsHCDczppukroy6ACaCgIqamWuRLHxGUnIfgEhaaEdEdmVnuiohexckeXaudUAcvWNUFMtbXQ0yABiwEL2cxAAbtAAPAAqAHzIALyEEgDaANYQ6MigyE-olDDIp8hwLD9Hs8ALrICAAD0gIAAJgCQIc1AB+fgI5bMD7A5inIHoYHiaLiIjrFAAGQgcCO5mUACsIAgwOYQEJ0GdLjchjjXiB3s8vj9Mcg5pQQHNNHBlEIIGcccDLhCobDkJQaXSwMhkdiMcgFKjRMhWnFicgqbT6YzmazrlsdntUccyRSLCqzUyWRdznEiAhhWxkAIyNC4JAjAJlAAFWZ0K3HCSnU7UMGQiAwgEfPkm1XmlkYMKZCDnAA0yAA9MXkEYrJRhNDkKpkFAIEVgPSIDWwJRWKYUAAiZWmsDdpWu4a0YCNovKARqysAd0RsZDykTCtTvO+AAohSKxRKpRmXRacwMkOc7nHqLKAJQF2MANTgQgEKHlycVW9FInFkuO+4ZruOR6JI4p7nmQspnouwJEOc64SJQHCcKAD6-swgHhEgN6kO2ZARlUdBYvGZCYawoa4SMWKLsRBwPk+WL3o+EBEJeqFYMeKBXJcQzeiAvrwcAiEgA+i6-la658QJyHOmAdzYWReigoiyIENEl5-ACaF5mehF4hIDZgAIUDcgQAB0pniUhQi-kWMnUHJ1QCiZZkIRZwlSdZLCkZGuLMNRDHRLEBJxNxvq+SxuadOy0RqX8IDoOI-qBsGnl4eg66+UW3YeHoXjdhlWXVKguXIN24oIN2l6iEAA
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5532 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 06:53 PVG 14:53 LAX 22:53 JFK 01:53
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86