告诉 TypeScript 应该允许 T 被实例化为任意类型

Telling TypeScript that T should be allowed to be instantiated with an arbitrary type

编辑: 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)

如何解决此输入问题?我认为告诉 TypeScript 关于 hoist-statics 中的“任何道具” T 然后在 Redirect 的道具中就足够了。

所以这是一个真正的错误,然后是一堆废话。

实际的错误是 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 继续你的生活。