告诉 TypeScript 应该允许使用任意类型实例化 T

问题描述

编辑: Here is a link to a minimal reproducible example. 我必须包含外部库 Redux 和 React Router Dom 才能忠实地重现它。


我正在使用 Redux 和 TypeScript 构建 Next.js 应用。

我有一个高阶组件来提升静力学。

import hoistNonReactStatics from 'hoist-non-react-statics';

type HOC<Inner,Outer = Inner> = (
  Component: React.ComponentType<Inner>,) => React.ComponentType<Outer>;

const hoistStatics = <I,O>(higherOrderComponent: HOC<I,O>): HOC<I,O> => (
  BaseComponent: React.ComponentType<I>,) => {
  const NewComponent = higherOrderComponent(BaseComponent);
  hoistNonReactStatics(NewComponent,BaseComponent);
  return NewComponent;
};

export default hoistStatics;

使用这个,我想编写一个 redirect HOC,可以根据 Redux 状态重定向用户。这是我所拥有的。

import Router from 'next/router.js';
import { curry } from 'ramda';
import { useEffect } from 'react';
import { connect,ConnectedProps } from 'react-redux';
import { RootState } from 'redux/root-reducer';

import hoistStatics from './hoist-statics';

function redirect(predicate: (state: RootState) => boolean,path: string) {
  const isExternal = path.startsWith('http');

  const mapStatetoProps = (state: RootState) => ({
    shouldRedirect: predicate(state),});

  const connector = connect(mapStatetoProps);

  return hoistStatics(function <T>(
    Component: React.ComponentType<Omit<T,'shouldRedirect'>>,) {
    function Redirect({
      shouldRedirect,...props
    }: T & ConnectedProps<typeof connector>): JSX.Element {
      useEffect(() => {
        if (shouldRedirect) {
          if (isExternal && window) {
            window.location.assign(path);
          } else {
            Router.push(path);
          }
        }
      },[shouldRedirect]);

      return <Component {...props} />;
    }

    return connector(Redirect);
  });
}

export default curry(redirect);

这里的问题是 return connector(Redirect); 会引发以下 TS 错误

Argument of type '({ shouldRedirect,...props }: T & { shouldRedirect: boolean; } & dispatchProp<AnyAction>) => Element' is not assignable to parameter of type 'ComponentType<Matching<{ shouldRedirect: boolean; } & dispatchProp<AnyAction>,T & { shouldRedirect: boolean; } & dispatchProp<AnyAction>>>'.
  Type '({ shouldRedirect,...props }: T & { shouldRedirect: boolean; } & dispatchProp<AnyAction>) => Element' is not assignable to type 'FunctionComponent<Matching<{ shouldRedirect: boolean; } & dispatchProp<AnyAction>,T & { shouldRedirect: boolean; } & dispatchProp<AnyAction>>>'.
    Types of parameters '__0' and 'props' are incompatible.
      Type 'PropsWithChildren<Matching<{ shouldRedirect: boolean; } & dispatchProp<AnyAction>,T & { shouldRedirect: boolean; } & dispatchProp<AnyAction>>>' is not assignable to type 'T & { shouldRedirect: boolean; } & dispatchProp<AnyAction>'.
        Type 'PropsWithChildren<Matching<{ shouldRedirect: boolean; } & dispatchProp<AnyAction>,T & { shouldRedirect: boolean; } & dispatchProp<AnyAction>>>' is not assignable to type 'T'.
          'T' Could be instantiated with an arbitrary type which Could be unrelated to 'PropsWithChildren<Matching<{ shouldRedirect: boolean; } & dispatchProp<AnyAction>,T & { shouldRedirect: boolean; } & dispatchProp<AnyAction>>>'.ts(2345)

我该如何解决这种打字问题?我认为将 T 中的“任何道具”hoist-staticsRedirect 的道具中的“任何道具”告诉 TypeScript 就足够了。

解决方法

所以有一个实际的错误,然后是一堆废话。

实际的错误是 Redirect 需要使用 PropsWithChildren

废话与 react-redux 包中的 Matching 实用程序类型有关,该实用程序类型用于将注入的 props 与组件自己的 props 连接起来。

/**
 * A property P will be present if:
 * - it is present in DecorationTargetProps
 *
 * Its value will be dependent on the following conditions
 * - if property P is present in InjectedProps and its definition extends the definition
 *   in DecorationTargetProps,then its definition will be that of DecorationTargetProps[P]
 * - if property P is not present in InjectedProps then its definition will be that of
 *   DecorationTargetProps[P]
 * - if property P is present in InjectedProps but does not extend the
 *   DecorationTargetProps[P] definition,its definition will be that of InjectedProps[P]
 */
export type Matching<InjectedProps,DecorationTargetProps> = {
    [P in keyof DecorationTargetProps]: P extends keyof InjectedProps
        ? InjectedProps[P] extends DecorationTargetProps[P]
            ? DecorationTargetProps[P]
            : InjectedProps[P]
        : DecorationTargetProps[P];
};

我们在这里得到了这两种类型之间的错误,说 A<T> 不可分配给 B<T>

type A<T> = PropsWithChildren<Matching<{ shouldRedirect: boolean; } & DispatchProp<AnyAction>,T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>>>
type B<T> = PropsWithChildren<T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>>

这两种类型在 99.9% 的情况下基本相同。再次,我们正在研究什么奇怪的边缘情况会使它成为真的。

基本上唯一不起作用的情况是,如果 T 有一个类型比我们注入的更窄的类型 shouldRedirectdispatch,即。 true 而不是 boolean

InjectedProps{ shouldRedirect: boolean; } & DispatchProp<AnyAction>

DecorationTargetPropsT & { shouldRedirect: boolean; } & DispatchProp<AnyAction>>

老实说,我的大脑在思考哪个项目符号规则有问题,但我可以看到比较 this playground 的第 87 行和第 88 行的问题。

所以在这一点上......只需as any,然后继续你的生活。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...