今天看到一篇关于 noInfer
的文章,特此翻译过来,原文链接 www.totaltypescript.com/noinfer
TypeScript 在5.4版本带来了 NoInfer
新类型,让我们看看它是如何提高在ts在具体条件下的推导能力
TypeScript 5.4 brought a new utility type called
NoInfer
. Let's see how it can be used to improve TypeScript's inference behavior in certain situations.
什么是NoInfer
NoInfer
是一种可以用来阻止ts从泛型函数内部推导能力的类型
NoInfer
is a utility type that can be used to prevent TypeScript from inferring a type from inside a generic function.
我们稍后会考虑为什么会这么做,首先我们看一下他是如何工作的
We'll look at why you'd want to do this in a moment. But first, let's see it at work
想象一下我们有一个只返回形参类型的泛型函数
Imagine we have a generic function that just returns the type of the value passed in:
ts
const returnWhatIPassedIn = <T>(value: T) => value;
// hello
const result = returnWhatIPassedIn("hello");
在这个条件下,ts 推断出 result 类型是 hello
In this case, TypeScript infers the type of
result
to be"hello"
.
但是如果我们使用 NoInfer
包裹呢
But what if we were to wrap
value: T
withNoInfer
?
ts
const returnWhatIPassedIn = <T>(value: NoInfer<T>) => value;
// unknow
const result = returnWhatIPassedIn("hello");
NoInfer
阻止了value
从 T 推断出的类型,所以,result 被推断出是 unknown,我们必须显式的提供类型给returnWhatIPassedIn
才能获得想要获取的类型
NoInfer
has preventedvalue
from being a valid source of inference forT
. So now,result
is inferred asunknown
. We'd need to explicitly provide the type toreturnWhatIPassedIn
to get the desired return type:
ts
const result = returnWhatIPassedIn<"hello">("hello");
所以,为什么你想要这么做呢?
So, why would you want to do this?
NoInfer 可以解决的问题
想象一下,有一个函数,它有多个变量可以从一个类型推导出来
Let's imagine that you have a function that has multiple candidates for inferring a type parameter.
举一个例子,一个函数创建了一个有限状态机(FSM),这个FSM 有一个 initial
状态和一个 states
列表,这个 initial
必须是 states
的一员
A great example is a function that creates a finite state machine (FSM). The FSM has an
initial
state and a list ofstates
. Theinitial
state must be one of thestates
.
让我们使用 declare
去定义函数声明(这样我不用提供实现)
Let's use
declare
to define the function signature (so I don't have to provide an implementation):
ts
declare function createFSM<TState extends string>(config: {
initial: TState;
states: TState[];
}): TState;
注意到,有两处地方可能倒推出 TState 类型: initial
和 states
Notice that there are two possible places where TypeScript could infer the type
TState
from:initial
andstates
.
如果我们调用他,ts 会从 initial
和 states
两处推断
If we try to call it, TypeScript will infer
TState
from BOTHinitial
andstates
:
ts
const example = createFSM({
initial: "not-allowed",
states: ["open", "closed"],
});
// const example: "not-allowed" | "open" | "closed"
console.log(example);
但是这是一个问题,我们只想从 states
推断出 TState
类型,我们不想从 inital
推断类型,我们想要确保 inital
在 states
数组中
But this is a problem! We want TypeScript to infer
TState
fromstates
only. We don't wantinitial
to be a candidate for inference. We want to ensure thatinitial
is a valid state in thestates
array.
NoInfer 如何帮助解决
我们可以使用 NoInfer
去阻止 initial
作为推断候选
We can use
NoInfer
to preventinitial
from being a candidate for inference
ts
declare function createFSM<TState extends string>(config: {
initial: NoInfer<TState>;
states: TState[];
}): TState;
现在,我们调用 createFSM
, ts 只会从 states
中推导出 TState
类型,意味着我们正确的从 initial
属性中获取了一个错误
Now, when we call
createFSM
, TypeScript will inferTState
fromstates
only. This means that we correctly get an error from ourinitial
property:
ts
createFSM({
initial: "not-allowed",
//Type '"not-allowed"' is not assignable to type '"open" | "closed"'.
states: ["open", "closed"],
});
如果你使用 NoInfer
包裹 states
,states
只会有一个有效值,那就是 initial
If we swap
NoInfer
over tostates
, then the only validstates
will be the value ofinitial
:
ts
declare function createFSM<TState extends string>(config: {
initial: TState;
states: NoInfer<TState>[];
}): TState;
createFSM({
initial: "open",
states: ["closed"],
// Type '"closed"' is not assignable to type '"open"'.
});
所以,我们可以使用 NoInfer
来控制 ts 从函数内部泛型类型,当有多个运行时参数,每个参数都引用相同的类型参数时,这可能很有用。NoInfer
允许你控制ts应该从哪个参数进行推断
So, we can use
NoInfer
to control where TypeScript infers a type from inside a generic function. This can be useful when you have multiple runtime parameters each referencing the same type parameter.NoInfer
allows you to control which parameter TypeScript should infer the type from.