簡體   English   中英

TypeScript參數類型推斷失敗

[英]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

更新我假設在以下代碼中foof是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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM