typescript - 如何聲明一個函數來斷言兩個參數相等?
在 TypeScript 中,你可以將一個值傳遞給函數,並讓該函數斷言該值為 true,以便進行類型縮小。例如
function assertTrue(v: unknown, message: string): asserts v {
if (!v) {
throw new MySpecialError(message)
}
}
然而,如果你想要有一個更高層級的函數來構造一個 v。例如
function assertEqual(v: unknown, expected: unknown) {
assertTrue(v === expected, `expected ${v} to be ${expected}`)
}
如果你調用 assertEqual,你不會得到任何類型縮小。一個權宜的解決方法是
function assertEqual<T>(v: unknown, expected: T): asserts v is T {
assertTrue(v === expected, `expected ${v} to be ${expected}`)
}
但那需要像 assertEqual<'foo'>(v, 'foo') 這樣的調用,而這很容易出錯。
在此範例中,nameOf 和 nameOf3 都通過型別檢查,但 nameOf2 未通過:
export type Staged =
| {
stage: 'pre-named';
}
| {
stage: 'named';
name: string;
};
class MySpecialError extends Error {}
function assertTrue(v: unknown, message: string): asserts v {
if (!v) {
throw new MySpecialError(message)
}
}
function nameOf(staged: Staged) {
assertTrue(staged.stage === 'named', 'must be named')
return staged.name
}
function assertEqual<T>(v: unknown, expected: T): asserts v is T {
assertTrue(v === expected, `expected ${v} to be ${expected}`)
}
function nameOf2(staged: Staged) {
assertEqual(staged.stage, 'named')
return staged.name
}
function nameOf3(staged: Staged) {
assertEqual<'named'>(staged.stage, 'named')
return staged.name
}
答案#1
您可以像這樣從第二個參數推導出所斷言的通用類型信息:
TS 遊樂場
function assertStrictEquals <T>(actual: unknown, expected: T): asserts actual is T {
if (actual !== expected) throw new Error('Values not equal');
}
const expected = { msg: 'hello world' };
const actual: unknown = expected; // unknown
assertStrictEquals(actual, expected);
actual; // { msg: string; }
而且,正如 futur 所指出的,你可以使用 const 斷言來指示編譯器從參數推斷出字串字面值類型:
function nameOf2 (staged: Staged): string {
assertStrictEquals(staged.stage, 'named' as const);
return staged.name;
}
你可以創建一個斷言函數,專門用於推斷字串字面值:
function assertIsString <T extends string>(actual: string, expected: T): asserts actual is T {
assert(actual === expected, 'Strings not equal');
}
答案#2
試試使用範本字面型別:
type AssertType<T> = `${any & T}`;
function assertEqual<K, T extends (unknown extends T ? AssertType<K> : K)>(v: unknown, expected: T): asserts v is T {
assertTrue(v === expected, `expected ${v} to be ${expected}`)
}
function nameOf(staged: Staged) {
assertTrue(staged.stage === 'named', 'must be named')
return staged.name
}
function nameOf2(staged: Staged) {
assertEqual(staged.stage, 'named')
return staged.name
}
function nameOf3(staged: Staged) {
assertEqual<string, 'named'>(staged.stage, 'named')
return staged.name
}
在 TS Playground 的完整範例。
