【问题标题】:Typescript dynamic keys return value打字稿动态键返回值
【发布时间】:2021-04-10 02:57:35
【问题描述】:

我想根据提供的键名构建一个带有两个键的对象。

function dynamicKeys<KeyOne extends PropertyKey, KeyTwo extends PropertyKey>(
  keyOne?: KeyOne,
  keyTwo?: KeyTwo
) {
  const normalizeKeyOne = keyOne ?? ("foo" as const);
  const normalizeKeyTwo = keyTwo ?? ("bar" as const);

  return {
    [normalizeKeyOne]: {},
    [normalizeKeyTwo]: [],
  } as const;
}

问题是上面的代码返回了一个通用类型:

{
    readonly [x: string]: {};
}

有没有办法让它返回以下类型:

const v = dynamicKeys(); // return type should be { foo, bar }
const v2 = dynamicKeys('baz', 'boo'); // return type should be { baz, boo }

【问题讨论】:

    标签: typescript


    【解决方案1】:

    这里发生了一些事情,但您面临的主要问题是 TypeScript 很少为具有计算键的对象提供强类型。在您的情况下,密钥是通用类型,因此您在microsoft/TypeScript#21030 中将行为列为设计限制:密钥类型一直扩大到string。许多其他 GitHub 问题都涉及到这一点,特别是 microsoft/TypeScript#13948,这仍然是一个悬而未决的问题。目前还不清楚在不久的将来是否会在这里实现任何东西,因此我们必须通过找出所需的类型和asserting 的值来解决它。


    dynamicKeys() 的一种可能实现如下所示:

    function dynamicKeys<K1 extends PropertyKey = "foo", K2 extends PropertyKey = "bar">(
        keyOne?: K1,
        keyTwo?: K2
    ) {
        const normalizeKeyOne = keyOne ?? ("foo" as K1);
        const normalizeKeyTwo = keyTwo ?? ("bar" as K2);
    
        return {
            [normalizeKeyOne]: {},
            [normalizeKeyTwo]: [],
        } as { [P in K1 | K2]: P extends K1 ? {} : [] };
    }
    

    让我们验证它是否适用于正常用例:

    console.log(dynamicKeys().foo); // {}
    console.log(dynamicKeys().bar.length); // 0
    
    
    console.log(dynamicKeys("baz").baz); // {}
    console.log(dynamicKeys().bar.length); // 0
    
    console.log(dynamicKeys("baz", "qux").baz); // {}
    console.log(dynamicKeys("baz", "qux").qux.length); // 0
    

    看起来不错。


    在那个实现中,我使用了一些并不总是正确的技巧。

    第一个是我给出了default specifications for the generic parameters,这样如果编译器无法推断它们,它会分别回退到"foo""bar" 用于K1K2。当有人在没有一个或多个参数的情况下调用dynamicKeys() 时,就会发生这种失败的推理。这不太正确的原因是有人在调用dynamicKeys()时总是可以手动指定泛型参数,编译器不会抱怨:

    // don't do this
    try {
        dynamicKeys<"boo", "hiss">().hiss.length;
    } catch (e) {
        console.log(e); // dynamicKeys().hiss is undefined
    }
    

    第二个是返回类型{ [P in K1 | K2]: P extends K1 ? {} : [] }mapped type,它具有联合K1 | K2 中每个元素的键。对于K1 中的任意键,值类型为{},对于K2 中的任意键,值类型为[]。这适用于K1K2 都是单个literal type 的常见情况。这不太正确的原因是有人可以传入K1K2 本身就是union types 的参数,然后编译器会错误地认为输出的键比实际的多:

    // don't do this
    const oops = dynamicKeys(
        Math.random() < 0.5 ? "one" : "two",
        Math.random() < 0.5 ? "three" : "four"
    );
    /* const oops: {
        one: {};
        two: {};
        three: [];
        four: [];
    } */
    try {
        console.log(oops.three.length + oops.four.length);
    } catch (e) {
        console.log(e); // oops.three is undefined (or maybe oops.four is undefined)
    }
    

    这些问题中的每一个都可以自行解决或避免,但代价是使事情变得更加复杂。您可以按照this question 的答案中的一种方法来处理“可能是错误的默认值”行为。您可以按照this question 的答案中的一种方法来处理“键联合”行为。这取决于你想停在哪里。

    Playground link to code

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-09
      • 1970-01-01
      • 2020-05-21
      • 2019-07-24
      • 2021-05-14
      • 2018-07-30
      • 2019-09-26
      • 2021-12-04
      相关资源
      最近更新 更多