假設有一種型別:
interface ObjWithKnownKeys {
prop: string;
}
我需要一個包含這種型別物件的物件。我知道我可以這樣輸入:
interface ObjWithUnknownKeysA {
[key: string]: ObjWithKnownKeys;
}
const a: ObjWithUnknownKeysA = {};
// ? no error that 'someName' is possibly undefined
a.someName.prop;
for (const key in a) {
const entry = a[key];
// ?? no errors
entry.prop;
}
for (const [key, entry] of Object.entries(a)) {
// ?? no errors
entry.prop;
}
或者像這樣:
interface ObjWithUnknownKeysB {
[key: string]: ObjWithKnownKeys | undefined;
}
const b: ObjWithUnknownKeysB = {};
// ?? error that 'someName' is possibly undefined
b.someName.prop;
for (const key in b) {
const entry = b[key];
// ? error that entry is possibly undefined (but it's clear it's not for given key)
entry.prop;
}
for (const [key, entry] of Object.entries(b)) {
// ? error that entry is possibly undefined
entry.prop;
}
但是我應該如何輸入它以確保它始終按預期作業?
自動關閉后編輯:在標記為相似的執行緒中沒有提及 --noUncheckedIndexedAccess 標志。我認為這個標志是一個更好的答案。
uj5u.com熱心網友回復:
這是 TypeScript 的痛點之一。默認情況下,編譯器將具有索引簽名的型別視為存在并定義了相關鍵型別的每個可能屬性。這很方便,因為您不必在使用之前說服編譯器實際定義了一個屬性:
interface StringIndex {
[k: string]: string;
}
const str: StringIndex = { abc: "hello" };
str.abc.toUpperCase(); // okay
您可以輕松地迭代鍵/值:
for (const k in str) {
str[k].toUpperCase(); // okay
}
for (const v of Object.values(str)) {
v.toUpperCase(); // okay
}
// arrays have numeric index signatures
const arr: Array<string> = ["x", "y", "z"];
for (const s of arr) {
s.toUpperCase(); // okay
}
不幸的是,它顯然是不安全的:
str.boop.toUpperCase(); // no compiler error! ??
因此,在microsoft/TypeScript#13778上有一個長期的功能請求,要求編譯器查看從索引簽名中讀取可以為您提供undefined. 這得到了開發者社區的大力支持。
一直以來,要想安全,只能手動添加| undefined屬性值型別。這讓事情變得更安全,但現在你必須不斷告訴編譯器你的屬性已定義,并且你也可以寫入 undefined一個屬性:
interface StringIndex {
[k: string]: string | undefined; // manually add undefined
}
const str: StringIndex = { abc: "hello" };
str.abc.toUpperCase(); // error! possibly undefined
str.abc = undefined; // no error, but we don't want to allow this
并且迭代也被污染undefined:
for (const k in str) {
str[k].toUpperCase(); // error! ossibly undefined
}
for (const v of Object.values(str)) {
v.toUpperCase(); // error! ossibly undefined
}
const arr: Array<string | undefined> = ["x", "y", "z"];
for (const s of arr) {
s.toUpperCase(); // error! possibly undefined
}
這很煩人。
最終,TypeScript 引入了--noUncheckedIndexedAccess編譯器選項,它會自動添加您從索引簽名鍵讀取undefined的屬性型別,但不允許您對其進行寫入。并且對于迭代它不會添加回圈或/ : undefinedundefinedfor...ofObject.values()Object.entries()
// enable --noUncheckedIndexedAccess
interface StringIndex {
[k: string]: string;
}
const str: StringIndex = { abc: "hello" };
str.boop.toUpperCase(); // error! possibly undefined
str.abc = undefined; // error!
for (const v of Object.values(str)) {
v.toUpperCase(); // okay
}
const arr: Array<string> = ["x", "y", "z"];
for (const s of arr) {
s.toUpperCase(); // okay
}
但這并不完美。它仍然無法識別存在“已知”密鑰:
const str: StringIndex = { abc: "hello" };
str.abc.toUpperCase(); // error! still possibly undefined
It doesn't let you iterate over keys with for...in loops without taking account of undefined:
for (const k in str) {
str[k].toUpperCase(); // error, oops
}
So you get a different set of desirable/undesirable behavior with --noUncheckedIndexedAccess, but you never get "the right thing in all circumstances".
The reason why this happens is because the compiler doesn't track the identity of variables; it tracks their types. It can narrow the type of a variable, but it doesn't have a way to tag a variable as a "known present key of such-and-such object". The compiler simply doesn't know how to "do the right thing". Turning on --noUncheckedIndexedAccess makes things more annoying in general, and people start working around it instead of paying attention to it. That's why it is not part of the --strict suite of compiler options. From a comment on microsoft/TypeScript#13778:
Think of the two types of keys in the world: Those which you know do have a corresponding property in some object (safe), those which you don't know to have a corresponding property in some object (dangerous). You get the first kind of key, a "safe" key, by writing correct code like [a
forloop]. You get the second kind from key, the "dangerous" kind, from things like user inputs, or random JSON files from disk, or some list of keys which may be present but might not be.So if you have a key of the dangerous kind and index by it, it'd be nice to have
| undefinedin here. But the proposal isn't "Treat dangerous keys as dangerous", it's "Treat all keys, even safe ones, as dangerous". And once you start treating safe keys as dangerous, life really sucks. You write code likefor (let i = 0; i < arr.length; i ) { console.log(arr[i].name); }and TypeScript is complaining at you that
arr[i]might beundefinedeven though hey look I just @#%#ing tested for it. Now you get in the habit of writing code like this [with a non-null assertion], and it feels stupid:for (let i = 0; i < arr.length; i ) { console.log(arr[i]!.name); }Or maybe you write code like this:
function doSomething(myObj: T, yourObj: T) { for (const k of Object.keys(myObj)) { console.log(yourObj[k].name); } }and TypeScript says "Hey, that index expression might be
| undefined, so you dutifully "fix it" because you've seen this error 800 times already:function doSomething(myObj: T, yourObj: T) { for (const k of Object.keys(myObj)) { console.log(yourObj[k]!.name); } }But you didn't fix the bug. You meant to write
Object.keys(yourObj), or maybemyObj[k]. That's the worst kind of compiler error, because it's not actually helping you in any scenario - it's only applying the same ritual to every kind of expression, without regard for whether or not it was actually more dangerous than any other expression of the same form.I think of the old "Are you sure you want to delete this file?" dialog. If that dialog appeared every time you tried to delete a file, you would very quickly learn to hit
del ywhen you used to hitdel, and your chances of not deleting something important reset to the pre-dialog baseline. If instead the dialog only appeared when you were deleting files when they weren't going to the recycling bin, now you have meaningful safety. But we have no idea (nor could we) whether your object keys are safe or not, so showing the "Are you sure you want to index that object?" dialog every time you do it isn't likely to find bugs at a better rate than not showing it all.
So because the compiler can't tell the difference between "safe" keys and "dangerous" keys, all you can do is treat all keys the same and decide which failure mode you hate less. The language default is that false negatives are a lesser evil than false positives. If you disagree you can turn on --noUncheckedIndexedAccess or add | undefined manually to individual types. But, at least in TS4.6, there's nothing better.
Playground link to code, --noUncheckedIndexedAccess turned off
Playground link to code, --noUncheckedIndexedAccess turned on
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/442128.html
標籤:javascript 打字稿 目的 类型
上一篇:將物件的方法傳遞給Javascript中的函式。這是對問題的正確解釋嗎?
下一篇:了解Lua中object.function(argument)和object:function(argument)之間的區別
