我最近構建了一個Builder類,在將構建器鏈與最終的execute().
我認為這個檢查可以在編譯時靜態完成,因此我想出了這個解決方案(為了示例而簡化):
interface abc {
a: string; // required
b: string; // required
c: string; // optional
}
class Builder<T = void> {
protected state: T = {} as any; // necessary ugly cast
public a(aa: string): Builder<T & Pick<abc, 'a'>> {
Object.defineProperty(this.state, 'a', {
value: aa,
writable: true,
});
return this as unknown as Builder<T & Pick<abc, 'a'>>;
}
public b(bb: string): Builder<T & Pick<abc, 'b'>> {
Object.defineProperty(this.state, 'b', {
value: bb,
writable: true,
});
return this as unknown as Builder<T & Pick<abc, 'b'>>;
}
public c(cc: string): Builder<T & Pick<abc, 'c'>> {
Object.defineProperty(this.state, 'c', {
value: cc,
writable: true,
});
return this as unknown as Builder<T & Pick<abc, 'c'>>;
}
public execute(this: Builder<Pick<abc, 'a' | 'b'>>) {
return this.state;
}
}
現在,當我這樣做時:
new Builder().a('foo').c('bar').execute();
我得到了我想要的錯誤,即: The 'this' context of type ... is not assignable to method's 'this' of type ...,這正是我想要的。
但是,當我編譯此打字稿代碼并在另一個專案中匯入構建檔案時,我不再收到此錯誤。
這是編譯后的型別定義:
interface abc {
a: string;
b: string;
c: string;
}
declare class Builder<T> {
protected state: T;
a(aa: string): Builder<T & Pick<abc, 'a'>>;
b(bb: string): Builder<T & Pick<abc, 'b'>>;
c(cc: string): Builder<T & Pick<abc, 'c'>>;
execute(this: Builder<Pick<abc, 'a' | 'b'>>): Pick<abc, "a" | "b">;
}
這是我的 tsconfig.json (我已經排除了專案路徑以保持與主題相關):
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"strict": true,
"importHelpers": true,
"declaration": true,
"declarationDir": "dist/esm/types/",
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"noImplicitAny": true,
"lib": [
"ESNext",
"webworker",
"DOM"
]
}
}
雪上加霜的是,當滑鼠懸停在變數上時,IntelliSense 會向我顯示這些型別(盡管我知道這不一定與編譯器相關):
new Builder().execute(); // should be an error
// with the type...
Builder<void>.send(this: Builder<Pick<abc, "a" | "b">>): ...
這是預期的行為嗎?我需要更改 tsconfig.json 嗎?難道我做錯了什么?如果我不編譯該類,那么它就可以完美運行。
uj5u.com熱心網友回復:
您應該知道這一行是 UNSAFE: protected state: T = {} as any;。
請考慮這種方法:
class Builder {
state = {}
public a<Param extends string>(aa: Param) {
Object.assign(this.state, { a: aa })
return this as this & { state: { a: Param } }
}
public b<Param extends string>(bb: Param) {
Object.assign(this.state, { b: bb })
return this as this & { state: { b: Param } }
}
public c<Param extends string>(cc: Param) {
Object.assign(this.state, { c: cc })
return this as this & { state: { c: Param } }
}
public execute() {
return this.state;
}
}
new Builder().a('foo').state.a // foo
new Builder().a('foo').b('bar').state.b // bar
操場
uj5u.com熱心網友回復:
在 yossarian 船長的幫助下,我意識到出了什么問題以及為什么我會遇到這個問題。
我的方法的問題
在玩了兩年的型別之后,我在我的腦海中錯誤地分離了型別和物體。實際上,它們在 TypeScript 中更加巧妙地交織在一起。雖然使用泛型是條件遞回映射型別的一種很好的方法,但這個用例要簡單得多,正如船長 yossarian 所證明的那樣。
我不需要在泛型中裝箱一個型別,只是為了在每個后續呼叫中逐步修改它。this本身就是一種型別,它在這里被證明是最有幫助的。
畢竟,類只是另一種型別。所以我在構建物體時不需要一些外部介面來構建。我本可以使用物體本身來表示“漸進式”型別。
(不過,如果您有這樣的外部介面,Pick<Type, Keys>非常適合此用例)
應用建議后的解決方案
解決方案與船長約索里安建議的非常相似。添加了幾行來完成我的問題的解決方案。
我使用了另一個介面,因為它是我實際測驗過的唯一實作。乍一看也更容易理解。
interface ABC {
a: string;
b: string;
c: string;
}
class Builder {
state = {};
public a(aa: string) {
Object.assign(this.state, { a: aa });
return this as this & { state: Pick<ABC, 'a'> };
}
public b(bb: Param) {
Object.assign(this.state, { b: bb });
return this as this & { state: Pick<ABC, 'b'> };
}
public c(cc: Param) {
Object.assign(this.state, { c: cc });
return this as this & { state: Pick<ABC, 'c'> };
}
public execute(this: this & { state: Pick<ABC, 'a' | 'b'> }) {
return this.state;
}
}
好的部分是這在編譯后也可以作業。發生了普遍的“勝利”局面,但是……
不足之處
您不能創建 state private,否則會產生型別交集never(因為兩者this和您的界面都為 提供了一個值state,一個是私有的)。通用方法雖然更笨拙,但在私有欄位中確實適用。從這個意義上說,我們可以只使用一個狀態來標記進度,但是如果我們不直接輸入狀態,那么型別檢查有什么意義呢?
發生了什么
That I can't really tell. I'm sure that the compiler shouldn't produce results that work differently from when the code is not compiled. I'm not sure what was lost on the way, but I'm curious to find out more about this. Like with many other things, maybe this will be fixed.
Conclusion
Types should/can mainly be used to model the data. This is their primary use, and it is a powerful tool when used correctly. I think using advanced types for interesting operations is useful, but sometimes (as it was in this case) doing the entire mechanism that way was overkill.
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/315149.html
上一篇:打字稿中的pick<>陣列型別
