首页 > 解决方案 > 定义具有类型约束的值,同时在打字稿中保持值的窄类型

问题描述

我想声明一个扩展特定类型并同时保持其窄类型的值。

有没有办法在不调用函数的情况下实现这一点?

const stringRecord : <T extends Record<string, string>>(x: T)=>T= (x) => x;


//Define value that extends Record<string, string> without calling a function 
const abc = stringRecord({
      a: "a",
      b: "b",
      c: "c"
  });


//Type should remain {a: string, b: string, c: string}
type ABC = typeof abc;

链接到游乐场

标签: typescripttypescript-generics

解决方案


不,目前(从 TypeScript 4.4 开始)没有类型运算符可以检查一个值是否可以分配给给定的(非联合)类型,而不会将其扩展到该类型。在microsoft/TypeScript#7481有一个长期开放的功能请求这样的运算符。如果您想看到这种情况发生,您可能会去那个问题并给它一个或描述您的用例以及为什么现有的解决方法是不够的;但我不认为一个额外的声音可能会产生很大的影响。

不幸的是,目前只有解决方法。

这个答案的其余部分将简要讨论其中的一些;尽管由于它们不被您接受,您可以忽略它。考虑讨论学术性的,或者可能对未来的读者有帮助。


在我看来,使用辅助函数的问题的解决方法是一个很好的解决方法。它对运行时的影响很小,但使用起来并不太麻烦。


如果您根本不想要任何运行时影响,您可以编写一些类型函数来做类似但纯粹在类型系统中的事情:

const abc = {
    a: "a",
    b: "b",
    c: "c"
};

type ABC = typeof abc;
type Extends<T extends U, U> = void;
type TestABC = Extends<ABC, Record<string, string>>; // okay

Extends<T, U>type 函数不会评估任何有用的东西(它只是) ,但如果不扩展void,它将引发编译器警告。观察:TU

const abc = {
    a: "a",
    b: 123, // <-- not a string
    c: "c"
};

type ABC = typeof abc;
type Extends<T extends U, U> = void;
type TestABC = Extends<ABC, Record<string, string>>; // error!
// ------------------> ~~~
// Property 'b' is incompatible 

所有额外的东西都会在运行时被删除。


最后,最简单的解决方法是完全忽略约束并依赖其他地方的东西来引发编译器错误。也就是说,如果你只写

const abc = {
    a: "a",
    b: "b",
    c: "c"
}; 

继续前进,大概在您的代码库中的其他地方,您将abc在需要Record<string, string>. 也许通过将它传递给您实际想要使用的某个函数:

declare function doSomethingLater(x: Record<string, string>): void;

如果它在没有编译器警告的情况下工作,那么很好:

doSomethingLater(abc); // okay

如果没有,警告会告诉你出了什么问题abc

doSomethingLater(abc); // error!
/* Argument of type '{ a: string; b: number; c: string; }' is not 
assignable to parameter of type 'Record<string, string>' */

如果您的代码库中没有代码抱怨 when abcis not assignable to Record<string, string>,那么您可能需要退后一步考虑为什么会这样。也许您实际上并不需要这样的约束,因为代码并不关心。但是,如果没有警告并不意味着一切都很好,那么您可能需要回退到以前的解决方法之一。


Playground 代码链接


推荐阅读