![](/img/trans.png)
[英]Typescript: checking truthiness of variable doesn't type-guard against undefined?
[英]Is there a way to create a type-guard for nested key access in Typescript?
我正在修改以前使用 vanilla Javascript 制作的實現。 我遇到了一個有趣的情況,我確實有解決方案,但我認為我有一個似乎不起作用的卓越解決方案。
基本上,我有一個帶有兩個嵌套對象的對象,它們的索引被鍵入為其鍵的字符串文字,其中兩個對象都有一些其他對象鍵,但沒有完全重疊。 然后,我有一個可以接收任一鍵的函數,以及第二個鍵來訪問該對象中的一個值。 我為鍵創建了自定義類型保護,並創建了第二組類型保護以確認傳遞的第二個鍵是其中一個對象的鍵。 然后,我創建了一個接受兩個鍵的函數,並且應該返回一個具有正確匹配鍵的對象。 但是,在函數中,typescript 似乎並不相信我正在使用我的驗證器函數,從我的函數返回的鍵只是一個絕對可以訪問上層對象的鍵的鍵。
這是一個非常不清楚的解釋,我認為一些示例代碼會使其更好,所以這里是:
const decoder = {
foo: {
foo: "bar",
bar: "foo"
},
bar: {
foo: "bar",
},
};
type FooKeys = keyof typeof decoder["foo"];
type BarKeys = keyof typeof decoder["bar"];
const isFooKey = (key: string): key is FooKeys => Object.prototype.hasOwnProperty.call(decoder["foo"], key);
const isBarKey = (key: string): key is BarKeys => Object.prototype.hasOwnProperty.call(decoder["bar"], key);
const isFoo = (key: string): key is "foo" => key === "foo";
const isBar = (key: string): key is "bar" => key === "bar";
const validator = (key: string, secondKey: string) => {
if (isFoo(key) && isFooKey(secondKey)) {
return { key, secondKey }
}
if (isBar(key) && isBarKey(secondKey)) {
return { key, secondKey }
}
return false;
}
// Here comes where the issue arises
const someFunc = (key: string, nestedKey: string) => {
const validated = validator(key, nestedKey);
if (validated) {
return decoder[validated.key][validated.secondKey];
}
return null;
}
任何人都可以向我解釋為什么這不能以我應該的方式工作,這是打字稿的缺點還是我的推理或實施的問題? 如果有人對我的問題有更好的解決方案,我很樂意聽到!
這里的潛在問題是,我一直稱之為相關記錄類型的已validated
,而 TypeScript 並沒有真正為這些提供很好的支持。 問題是decoder[validated.key]
和validated.secondKey
的類型都是union類型; 前者的類型為{ foo: string; bar: string; } | { foo: string; }
{ foo: string; bar: string; } | { foo: string; }
{ foo: string; bar: string; } | { foo: string; }
,后者為類型"foo" | "bar"
"foo" | "bar"
。 TypeScript 的類型系統幾乎無法表示它們之間存在相關性的事實。
一般來說,如果我有兩個聯合類型的值,其中每個聯合類型都有兩個成員,比如declare const x: A | B; declare const y: C | D;
declare const x: A | B; declare const y: C | D;
中,一對[x, y]
將類似於一個類型的[A, C] | [A, D] | [B, C] | [B, D]
[A, C] | [A, D] | [B, C] | [B, D]
[A, C] | [A, D] | [B, C] | [B, D]
。 但是您碰巧知道,例如,如果x
是A
類型,那么y
將是C
類型,反之亦然……因為x
的類型與y
的類型相關。 所以[A, D]
和[B, C]
是不可能的。 因此[x, y]
應該只屬於[A, C] | [B, D]
[A, C] | [B, D]
。 但是編譯器本身無法推斷出這一點,因此它會抱怨這些不可能的情況。
在您的情況下,編譯器無法驗證decoder[validated.key][validated.secondKey]
將成為有效的索引操作。 它認為decoder[validated.key]
可能是{ foo: string }
而validated.secondKey
可能是"bar"
。 所以它抱怨。
您可以做一些事情來解決這個問題。 一種是使用類型斷言告訴編譯器不要擔心。 這是最不安全的類型,但它不會更改您的運行時代碼:
(decoder[validated.key] as { foo: string, bar: string })[validated.key]
您基本上聲稱decoder[validated.key]
是其兩種可能類型的交集,因此您可以安全地索引其"foo" | "bar"
"foo" | "bar"
屬性。
您可以編寫冗余代碼來引導編譯器通過兩種可能性:
validated.key == "foo" ?
decoder[validated.key][validated.secondKey] :
decoder[validated.key][validated.secondKey]
這里它使用控制流分析將validated
范圍縮小到兩種可能性,之后validated.key
和validated.secondKey
不再是聯合類型。
這些是一般處理相關記錄的兩種主要方法。 對於您的代碼,有第三種可能性本身:由於您的validator()
函數實際上分別經歷了這兩種可能性,您可以將索引移動到該函數中以利用控制流分析:
const validatorAndDecoder = (key: string, secondKey: string) => {
if (isFoo(key) && isFooKey(secondKey)) {
return { key, secondKey, val: decoder[key][secondKey] }
}
if (isBar(key) && isBarKey(secondKey)) {
return { key, secondKey, val: decoder[key][secondKey] }
}
return null;
}
const someFunc2 = (key: string, nestedKey: string) => {
const validated = validatorAndDecoder(key, nestedKey);
if (validated) {
return validated.val
}
return null;
}
好的,希望有幫助; 祝你好運!
你可以這樣做:
function pluck<T, K extends keyof T>(o: T, propertyName: K): T[K] {
return o[propertyName];
}
function validator(obj: typeof decoder, key: string, secondKey: string) {
if (isFoo(key) && isFooKey(secondKey)) {
const item = pluck(obj, key);
const subItem = pluck(item, secondKey);
return subItem;
}
if (isBar(key) && isBarKey(secondKey)) {
const item = pluck(obj, key);
const subItem = pluck(item, secondKey);
return subItem;
}
return null;
}
索引類型
使用索引類型,您可以讓編譯器檢查使用動態屬性名稱的代碼。
看這里( pluck
)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.