【问题标题】:Question about type inference when using Generic and rest parameters关于使用泛型和剩余参数时类型推断的问题
【发布时间】:2021-03-01 07:56:30
【问题描述】:

这个问题最重要的是,激发我的好奇心,加深我对使用泛型时 typescript 类型推断的内部工作原理的理解。

问题

我想创建一个高阶函数,为接收的函数添加额外的参数:

type WithConfirmProps = {
  action: Function;
  text: string;
}

type CustomFunction<P> = (arg: P) => void

export function withConfirm<P>(innerFunction: CustomFunction<P>) {
  const wrappedFunction = ({ action, text, ...rest }: P & WithConfirmProps) => {
    // ...
    // code that uses action and text...

    rest; // inferred type is 'Pick<P & WithConfirmProps, Exclude<keyof P, "action" | "text">>'
    // but I expected it to be simply 'P', 
    // or at least that the 'Exclude' would be applied to 'WithConfirmProps', not to 'P'

    return innerFunction(rest); 
    //        error here ˆˆˆˆ is:
    //   Argument of type 'Pick<P & WithConfirmProps, Exclude<keyof P, "action" | "text">>' is not assignable to parameter of type 'P'.
    //     'P' could be instantiated with an arbitrary type which could be unrelated to 'Pick<P & WithConfirmProps, Exclude<keyof P, "action" | "text">>'.(2345)
  };
​
  return wrappedFunction;
}

我发现错误信息有些难以理解,但经过一番思考,我对问题的解释是:

P 类型本身可以具有actiontext 属性,这就是为什么rest 不能被推断为P 的原因

我的解决方案

根据上面的解释,我试图向编译器传达通用P 实际上没有actiontext 属性。

下面的sn-p显示没有编译错误:

export function withConfirm<P>(innerFunction: CustomFunction<Pick<P, Exclude<keyof P, 'action' | 'text'>>>) {
  const wrappedFunction = ({ action, text, ...rest }: P & WithConfirmProps) => {
    // ...
    // code that uses action and text...

    rest; // type is Pick<P & WithConfirmProps, Exclude<keyof P, "action" | "text">>
    
    return innerFunction(rest); // 'rest' has exactly the same inferred type, 
    // but now it does not error ????‍♂️
  };
​
  return wrappedFunction;
}

你可以在这个typescript playground中重现这个问题


  • 问题 1:我对错误的解释是否正确?
  • 问题 2:有没有更好的方法来键入函数?

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    类型 P 本身可以具有属性actiontext,这就是为什么不能将rest 推断为P

    就是这样。由对象解构创建的rest 变量永远不会包含属性actiontext。如果innerFunction 需要具有actiontext 属性的类型,则rest 将无法分配给此类型,因为它将缺少这些属性。

    您已经在第二个示例中排除了这种可能性。你说过不管P 是什么类型,innerFunction 绝对不能要求具有actiontext 属性的对象。因此,当我们删除这些属性时,我们不必担心 rest 会丢失 innerFunction 所期望的任何内容。

    另一种看起来更简洁的处理方法是将解构移动到函数体内。这样我们就可以用整个props 对象而不是rest 来调用innerFunction。我们已经知道props 可以分配给P,我们不必担心任何丢弃的属性。

    export function withConfirm<P>(innerFunction: CustomFunction<P>) {
        const wrappedFunction = (props: P & WithConfirmProps) => {
            const { action, text } = props;
    
            // do stuff with action and text...
    
            return innerFunction(props);
        };
    
        return wrappedFunction;
    }
    

    与我上面写的相比,您的第二种方法的一个优点是它清楚地说明了哪些内部函数会导致问题。

    const inner = ({action}: {action: string}) => action
    

    考虑上面的函数inner,其中P{action: string}。当我们包裹在高阶函数中时,我们不会得到任何错误。

    const wrapped = withConfirm(inner);
    

    但是我们已经创建了一个函数wrapped,它期望被P &amp; WithConfirmProps aka {action: string} & {action: Function; text: string;} 调用,这是不可能的。你永远不能调用这个函数,因为你总是会得到一个错误,你的action 参数是not assignable to type 'string &amp; Function'

    输入innerFunction: CustomFunction&lt;Pick&lt;P, Exclude&lt;keyof P, 'action' | 'text'&gt;&gt;&gt; 后,在尝试包装函数时会出现错误。所以我认为你的打字工作做得很好。我会将其写为 innerFunction: CustomFunction&lt;Omit&lt;P, keyof WithConfirmProps&gt;&gt; 以避免字符串文字,但它的计算结果相同。

    【讨论】:

      猜你喜欢
      • 2020-06-09
      • 1970-01-01
      • 2020-01-25
      • 2021-11-06
      • 1970-01-01
      • 2012-09-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多