问题描述
编辑: 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-statics
和 Redirect
的道具中的“任何道具”告诉 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
有一个类型比我们注入的更窄的类型 shouldRedirect
或 dispatch
,即。 true
而不是 boolean
。
InjectedProps
是 { shouldRedirect: boolean; } & DispatchProp<AnyAction>
DecorationTargetProps
是 T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>>
老实说,我的大脑在思考哪个项目符号规则有问题,但我可以看到比较 this playground 的第 87 行和第 88 行的问题。
所以在这一点上......只需as any
,然后继续你的生活。