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