我試圖準確地描述一個簡單的omit()函式:
function omit(source, props) {
return Object.fromEntries(
Object.entries(source).filter(entry => {
if (Array.isArray(props)) {
return !props.includes(entry[0]);
}
return entry[0] !== props;
}),
);
}
下面我將描述我的三種方法以及它們失敗的原因。如果您清楚地了解問題并知道答案,則可以跳過它并繼續回答。:)
方法一
操場
declare function omit<
O extends Record<string, unknown>, P extends string | string[]
>(
source: O, props: P
): Omit<
O, P extends string ? P : P[number]
>;
const o1 = omit({ a: 1, b: 'foo', c: true }, 'b');
o1.a; // no error - correct
o1.b; // error - correct
o1.c; // no error - correct
const o2 = omit({ a: 1, b: 'foo', c: true }, ['b', 'c']);
o2.a; // error - wrong!
o2.b; // error - correct
o2.c; // error - correct
這對我來說是最明顯的解決方案。
不幸的是,該o2.a運算式產生了錯誤Property 'a' does not exist on type 'Omit<{ a: number; b: string; c: boolean; }, string>'。顯然,TS 丟失了特定的字串型別string[]并將它們合并到一個通用的string.
方法二
操場
declare function omit<
O extends Record<string, unknown>,
P1 extends string,
P2 extends string,
P3 extends string,
P extends string | [P1] | [P1, P2] | [P1, P2, P3]
>(
source: O, props: P
): Omit<
O, P extends string ? P : P[number]
>;
const o1 = omit({ a: 1, b: 'foo', c: true }, 'b');
o1.a; // no error - correct
o1.b; // error - correct
o1.c; // no error - correct
const o2 = omit({ a: 1, b: 'foo', c: true }, ['b', 'c']);
o2.a; // no error - correct
o2.b; // error - correct
o2.c; // error - correct
這種方法作業得很好,但有一個明顯的缺點——不可能使用(在函式型別中描述)任意數量的要省略的屬性。
方法三
操場
declare function omit<
O extends object,
P extends string[]
>(
source: O,
...props: P
): Omit<O, P[number]>;
const o1 = omit({ a: 1, b: 'foo', c: true }, 'b');
o1.a; // no error - correct
o1.b; // error - correct
o1.c; // no error - correct
const o2 = omit({ a: 1, b: 'foo', c: true }, 'b', 'c');
o2.a; // no error - correct
o2.b; // error - correct
o2.c; // error - correct
我從 immortallodash的 typedef 中竊取了解決方案。令我驚訝的是它有效!
For some reason (please tell me if you understand why) TS saves the specific string[] type for the rest parameters, but not for an array in a regular parameter.
Alas, this solution is still not what I'm looking for: it uses rest parameters, not the single regular parameter.
So...
Is there a way to describe my specific function? Why does TS save a specific string[] type in a generic for a rest parameters, but not for an array in a regular parameter?
Thank you.
uj5u.com熱心網友回復:
當涉及到讓編譯器查看字串文字值"a"并保持其型別為文字型別 "a"而不是擴展到string型別時,它更像是一門藝術而不是一門科學。如果 的用戶omit()愿意明確地表達他們的意圖,例如,通過使用constassertion,那么即使使用方法 #1,事情也會開始為您作業:
const o2 = omit({ a: 1, b: 'foo', c: true }, ['b' as const, 'c' as const]);
o2.a; // okay
o2.b; // error
o2.c; // error
否則,您將依賴編譯器使用的啟發式規則。如果您查看microsoft/TypeScript#10676,您可以看到這些規則的串列。
對我來說最一致的方法是:創建一個限制為string(對于字串文字)的泛型型別引數,例如P extends string,然后在我希望編譯器將字串文字視為字串文字型別的任何地方使用該型別引數。這導致以下實作omit():
declare function omit<
O extends Record<string, unknown>, P extends string
>(
source: O, props: P | P[]
): Omit<O, P>;
現在您可以看到一切都按預期運行:
const o1 = omit({ a: 1, b: 'foo', c: true }, 'b');
// function omit<{ ... }, "b">(...)
o1.a; // okay
o1.b; // error
o1.c; // okay
const o2 = omit({ a: 1, b: 'foo', c: true }, ['b', 'c']);
// function omit<{ ... }, "b" | "c">(...)
o2.a; // okay
o2.b; // error
o2.c; // error
當然,這些只是推理提示,并不能保證有人不會克服它們:
const oops = ["b", "c"];
// const oops: string[]
const o3 = omit({ a: 1, b: "foo", c: true }, oops)
// const o3: Omit<{ ... }, string>
這里用戶oops以編譯器推斷string[]其型別的方式宣告。這發生在omit()有機會做任何事情之前。因此P推斷引數是string因為oops是型別string[]。你會得到一個無用的型別。
我不知道如何建議你繼續那里。這可能不是什么大問題,或者您可能對當前的結果感到滿意。您可以使編譯器拒絕呼叫omit()ifP推斷string如下:
declare function omit<
O extends Record<string, unknown>, P extends string
>(
source: O, props: string extends P ? never : P | P[]
): Omit<O, P>;
對于好的示例,它的作業原理相同,但會導致以下呼叫發出警告:
const o3 = omit({ a: 1, b: "foo", c: true }, oops) // error!
// ----------------------------------------> ~~~~
// Argument of type 'string[]' is not assignable to parameter of type 'never'.
在處理邊緣情況時肯定會出現一些收益遞減的情況,所以我將不再贅述這一點。
Playground 鏈接到代碼
uj5u.com熱心網友回復:
你的函式最好用兩個多載來描述:
// Single prop
declare function omit<O extends Record<string, any>, P extends string>(
source: O,
prop: P
): Omit<O, P>
// Array of props
declare function omit<O extends Record<string, any>, P extends string>(
source: O,
props: P[]
): Omit<O, P>
// Then...
const o1 = omit({ a: 1, b: 'foo', c: true }, 'b');
o1.a; // no error - correct
o1.b; // error - correct
o1.c; // no error - correct
const o2 = omit({ a: 1, b: 'foo', c: true }, ['b', 'c']);
o2.a; // no error - correct!
o2.b; // error - correct
o2.c; // error - correct
// Allow prop values not in `O`
const o3 = omit({ a: 1, b: 'foo', c: true }, ['b', 'c', 'd'])
o3.a; // no error - correct
o3.b; // error - correct
o3.c; // error - correct
o3.d; // error - correct
這里還有一個到操場的鏈接。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/323544.html
