[英]TypeScript parameter type inference failure
我為此創建了一個錯誤報告 ,但也許有人想出一種解決方法。 基本上,我想根據另一個函數的類型有一個函數的參數:
declare function foo<P>(params: { f: (p: P) => void } & P): void;
foo({
f(params: { bar(x: number): void }) {},
bar(x) {},
});
因此,有一個特殊的參數f
具有一個函數類型,並且該函數期望的任何屬性都必須作為foo
參數包括在內。 此處再次將bar
的參數推斷為any
但應為number
。
每當我看到要求具有依賴於參數值的函數類型的要求時,首先想到的是“使用重載”:
declare function foo(params: { tag: 'a', bar(x: number): void }): void;
declare function foo(params: { tag: 'b', bar(x: boolean): void }): void;
foo({ tag: 'a', bar(x) { console.log('x =', x)} }); // (parameter) x: number
更新我假設在以下代碼中foo
和f
是React組件,並且foo
公開f
作為其屬性以及f
具有的所有屬性。
declare function foo<P>(params: { f: (p: P) => void } & P): void;
foo({
f(params: { bar(x: number): void }) {},
bar(x) {},
});
有一種方法可以重寫它,只要您不堅持要求f
的名稱(這是foo
的屬性)以及f
屬性的類型必須在調用foo
參數時內聯聲明。
您可以聲明一個描述此類組合組件屬性的類型,它采用兩個通用參數:內部組件屬性的類型和外部屬性中內部組件的名稱:
type ComposedProps<InnerProps, N extends string> =
InnerProps & { [n in N]: (props: InnerProps) => void };
// Also I assume in reality it does not return `void` but `React.Element`,
// but I don't think it matters
declare function foo<P>(params: P): void;
// foo generic parameter is given explicitly,
// which allows to infer (and not repeat) parameter types for `f` and `bar`
foo<ComposedProps<{bar(x: number): void}, 'f'>>({
f(params) {}, // params: { bar(x: number): void; }
bar(x) {}, // (parameter) x: number
});
正如我們在評論中討論的那樣,該場景實際上與反應組件以及新的通用組件功能與默認通用參數交互的方式有關。
問題
如果我們有一個具有通用參數的react組件,並且通用參數的默認值具有function字段,則不會推斷我們傳入的函數的參數。 一個簡單的例子是:
class Foo<P = { fn : (e: string)=> void }> extends React.Component<P>{}
let foo = () => (
<div>
<Foo fn={e=> e}/> {/* e is any */}
</div>
)
可能的解決方案
讓我先警告一下,我不知道它在所有情況下都適用,還是不取決於某些將改變的編譯器行為。 我的發現是,這是一個非常脆弱的解決方案,即使可以合理地認為是等效的並且無法使用,也可以更改任何細節。 我可以說的是,正如所介紹的那樣,它可以對我進行測試。
解決方案是利用以下事實:屬性類型是從構造函數的返回值中獲取的,如果我們對默認泛型參數進行了一些簡化,則可以得到所需的推論。 唯一的問題是檢測到我們正在處理默認的通用參數。 為此,我們可以在默認參數中添加一個額外的字段,然后使用條件類型對其進行測試。 請注意,這不適用於可以根據使用的屬性推斷出P
簡單情況,因此我們有一個示例,其中該組件從另一個組件提取屬性(實際上更接近於您的用例):
// Do not change { __default?:true } to DefaultGenericParameter it only works with the former for some reason
type IfDefaultParameter<C, Default, NonDefault> = C extends { __default?:true } ? Default : NonDefault
type DefaultGenericParameter = { __default? : true; }
export type InferredProps<C extends React.ReactType> =
C extends React.ComponentType<infer P> ? P :
C extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[C] :
{};
// The props for Foo, will extract props from the generic parameter passed in
type FooProps<T extends React.ReactType = React.ComponentClass<{ fn: (s: string )=> void }>> = InferredProps<T> & {
other?: string
}
// Actual component class
class _Foo<P> extends React.Component<P>{}
// The public facing constructor
const Foo : {
new <P extends React.ReactType & {__default?: true} = React.ComponentClass<{ fn: (s: string )=> void }> & DefaultGenericParameter>(p: P) :
IfDefaultParameter<P, _Foo<FooProps>, _Foo<FooProps<P>>>
} = _Foo as any;
let foo = () => (
<div>
<Foo fn={e=> e}/> {/* e is string */}
<Foo<React.ComponentClass<{ fn: (s: number )=> void }>> fn={e=> e}/> {/* e is number */}
</div>
)
應用於材料用戶界面
我在material-ui回購中嘗試過的一個示例是Avatar
組件,它似乎可以工作:
export type AvatarProps<C extends AnyComponent = AnyComponent> = StandardProps<
PassthruProps<C, 'div'>,
AvatarClassKey
> & {
alt?: string;
childrenClassName?: string;
component?: C;
imgProps?: React.HtmlHTMLAttributes<HTMLImageElement>;
sizes?: string;
src?: string;
srcSet?: string;
};
type IfDefaultParameter<C, Default, NonDefault> = C extends { __default?:true } ? Default : NonDefault
type DefaultGenericParameter = { __default? : true; }
declare const Avatar : {
new <C extends AnyComponent & DefaultGenericParameter = 'div' & DefaultGenericParameter>(props: C)
: IfDefaultParameter<C, React.Component<AvatarProps<'div'>>, React.Component<AvatarProps<C>>>
}
用法
const AvatarTest = () => (
<div>
{/* e is React.MouseEvent<HTMLDivElement>*/}
<Avatar onClick={e => log(e)} alt="Image Alt" src="example.jpg" />
{/* e is {/* e is React.MouseEvent<HTMLButtonElement>*/}*/}
<Avatar<'button'> onClick={e => log(e)} component="button" alt="Image Alt" src="example.jpg" />
<Avatar<React.SFC<{ x: number }>>
component={props => <div>{props.x}</div>} // props is props: {x: number;} & {children?: React.ReactNode;}
x={3} // ok
// IS NOT allowed:
// onClick={e => log(e)}
alt="Image Alt"
src="example.jpg"
/>
</div>
祝您好運,如果有幫助,請告訴我。 這是一個有趣的小問題,已經讓我醒了十天了:)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.