使用 Next.js 在 TypeScript 中输入重定向 HOC
Typing a redirect HOC in TypeScript with Next.js
我正在使用 TypeScript 构建一个 Next.js 项目。我正在尝试输入一个基于 Redux 状态重定向用户的 HOC。
这是我目前的情况:
import { RootState } from 'client/redux/root-reducer';
import Router from 'next/router.js';
import { curry } from 'ramda';
import React, { useEffect } from 'react';
import { connect, ConnectedProps } from 'react-redux';
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<T>) {
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 Redirect;
});
}
export default curry(redirect);
我觉得我已经很接近了,但不能完全理解最后一个错误。 <Component {...props} />
大喊:
Type 'Pick<T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>, "dispatch" | Exclude<keyof T, "shouldRedirect">>' is not assignable to type 'IntrinsicAttributes & T & { children?: ReactNode; }'.
Type 'Pick<T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>, "dispatch" | Exclude<keyof T, "shouldRedirect">>' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'Pick<T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>, "dispatch" | Exclude<keyof T, "shouldRedirect">>'.
这是怎么回事?我可以通过像这样输入内部 HOC 来使代码通过:
return hoistStatics(function <T>(Component: React.ComponentType<T>) {
function Redirect(
props: T & ConnectedProps<typeof connector>,
): JSX.Element {
useEffect(() => {
if (props.shouldRedirect) {
if (isExternal && window) {
window.location.assign(path);
} else {
Router.push(path);
}
}
}, [props.shouldRedirect]);
return <Component {...props} />;
}
return Redirect;
});
但这意味着 Component
得到了一个叫做 shouldRedirect
的道具,它不应该。它应该只接收并传递它通常接收的道具。
我不得不禁用两个警告。
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return hoistStatics(function <T>(Component: React.ComponentType<T>) {
和
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return connector(Redirect);
hoistStatics
是高阶 HOC,看起来像这样。
import hoistNonReactStatics from 'hoist-non-react-statics';
const hoistStatics = (
higherOrderComponent: <T>(
Component: React.ComponentType<T>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => (props: T & any) => JSX.Element,
) => (BaseComponent: React.ComponentType) => {
const NewComponent = higherOrderComponent(BaseComponent);
hoistNonReactStatics(NewComponent, BaseComponent);
return NewComponent;
};
export default hoistStatics;
它的类型也被 hack 在一起(正如您在禁用警告中看到的那样)。
这两个函数怎么打出来的?
编辑:
在 Linda 的帮助下 hoistStatics
现在可以工作了。 Redirect
仍然会导致问题。 .
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);
});
}
带有 connector(Redirect)
的行仍然抛出错误:
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>>>'.
错误
Typescript 在拼凑来自各种传播操作的 props 时可能会出错,因为边缘情况会导致无效对象。
return hoistStatics(function <T>(Component: React.ComponentType<T>) {
function Redirect({
shouldRedirect,
...props
}: T & ConnectedProps<typeof connector>): JSX.Element {
/*...*/
return <Component {...props} />;
}
组件道具T
可以是任何对象。如果 T
包含必需的 属性 shouldRedirect
.
,则打破这一点的边缘情况
我们在编写 {shouldRedirect, ...props}
时从 props 中解构了 shouldRedirect
。因此知道这个props
变量不可能包含一个变量shouldRedirect
。它是 Omit<T, 'shouldRedirect'>
类型。如果我们处于 T
具有必需的 shouldRedirect
道具的边缘情况,那么我们会在这里遇到问题:<Component {...props} />
因为 props
无法满足 T
.
你得到的错误很难阅读,因为它输出的类型太啰嗦了,但那个类型代表了 props
对象。 “'T' 可以用任意类型实例化”基本上意味着这些道具可能满足 T
或者它们可能不满足 T
,具体取决于 T
是什么。
解决方案
这在编写 HOC 时经常出现,如果遇到这种情况,您总是可以求助于 {...props as T}
。通常我所做的是传递整个对象,就像您在“我可以让代码通过”代码块中所做的那样。如果我们想要删除不需要的属性并保持其类型安全,我们需要禁止不良的边缘情况。我们保持 T
含糊不清,但声明我们的 Component
不能通过使用 Omit
.
来要求这个道具
function <T>(Component: React.ComponentType<Omit<T, 'shouldRedirect'>>)
输入 hoistStatics
hoistStatics
是一个接受 HOC 的函数,returns 是一个 HOC。我们知道 HOC 可以操纵 props,使得组合组件 (Outer
) 的 props 不同于基础组件 (Inner
) 的 props。所以我们可以在这里使用两个泛型。无论这两种类型是什么,hoistStatics
都不应该更改它们并且应该 return 一个与其参数具有相同签名的 HOC。
我们定义了一个通用的 HOC
签名来保持干净
type HOC<Inner, Outer = Inner> = (
Component: React.ComponentType<Inner>,
) => React.ComponentType<Outer>
然后我们用那个类型来描述hoistStatics
const hoistStatics = <I, O>(
higherOrderComponent: HOC<I, O>
): HOC<I, O> => (BaseComponent: React.ComponentType<I>) => {
const NewComponent = higherOrderComponent(BaseComponent);
hoistNonReactStatics(NewComponent, BaseComponent);
return NewComponent;
};
我正在使用 TypeScript 构建一个 Next.js 项目。我正在尝试输入一个基于 Redux 状态重定向用户的 HOC。
这是我目前的情况:
import { RootState } from 'client/redux/root-reducer';
import Router from 'next/router.js';
import { curry } from 'ramda';
import React, { useEffect } from 'react';
import { connect, ConnectedProps } from 'react-redux';
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<T>) {
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 Redirect;
});
}
export default curry(redirect);
我觉得我已经很接近了,但不能完全理解最后一个错误。 <Component {...props} />
大喊:
Type 'Pick<T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>, "dispatch" | Exclude<keyof T, "shouldRedirect">>' is not assignable to type 'IntrinsicAttributes & T & { children?: ReactNode; }'.
Type 'Pick<T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>, "dispatch" | Exclude<keyof T, "shouldRedirect">>' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'Pick<T & { shouldRedirect: boolean; } & DispatchProp<AnyAction>, "dispatch" | Exclude<keyof T, "shouldRedirect">>'.
这是怎么回事?我可以通过像这样输入内部 HOC 来使代码通过:
return hoistStatics(function <T>(Component: React.ComponentType<T>) {
function Redirect(
props: T & ConnectedProps<typeof connector>,
): JSX.Element {
useEffect(() => {
if (props.shouldRedirect) {
if (isExternal && window) {
window.location.assign(path);
} else {
Router.push(path);
}
}
}, [props.shouldRedirect]);
return <Component {...props} />;
}
return Redirect;
});
但这意味着 Component
得到了一个叫做 shouldRedirect
的道具,它不应该。它应该只接收并传递它通常接收的道具。
我不得不禁用两个警告。
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return hoistStatics(function <T>(Component: React.ComponentType<T>) {
和
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return connector(Redirect);
hoistStatics
是高阶 HOC,看起来像这样。
import hoistNonReactStatics from 'hoist-non-react-statics';
const hoistStatics = (
higherOrderComponent: <T>(
Component: React.ComponentType<T>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => (props: T & any) => JSX.Element,
) => (BaseComponent: React.ComponentType) => {
const NewComponent = higherOrderComponent(BaseComponent);
hoistNonReactStatics(NewComponent, BaseComponent);
return NewComponent;
};
export default hoistStatics;
它的类型也被 hack 在一起(正如您在禁用警告中看到的那样)。
这两个函数怎么打出来的?
编辑:
在 Linda 的帮助下 hoistStatics
现在可以工作了。 Redirect
仍然会导致问题。
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);
});
}
带有 connector(Redirect)
的行仍然抛出错误:
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>>>'.
错误
Typescript 在拼凑来自各种传播操作的 props 时可能会出错,因为边缘情况会导致无效对象。
return hoistStatics(function <T>(Component: React.ComponentType<T>) {
function Redirect({
shouldRedirect,
...props
}: T & ConnectedProps<typeof connector>): JSX.Element {
/*...*/
return <Component {...props} />;
}
组件道具T
可以是任何对象。如果 T
包含必需的 属性 shouldRedirect
.
我们在编写 {shouldRedirect, ...props}
时从 props 中解构了 shouldRedirect
。因此知道这个props
变量不可能包含一个变量shouldRedirect
。它是 Omit<T, 'shouldRedirect'>
类型。如果我们处于 T
具有必需的 shouldRedirect
道具的边缘情况,那么我们会在这里遇到问题:<Component {...props} />
因为 props
无法满足 T
.
你得到的错误很难阅读,因为它输出的类型太啰嗦了,但那个类型代表了 props
对象。 “'T' 可以用任意类型实例化”基本上意味着这些道具可能满足 T
或者它们可能不满足 T
,具体取决于 T
是什么。
解决方案
这在编写 HOC 时经常出现,如果遇到这种情况,您总是可以求助于 {...props as T}
。通常我所做的是传递整个对象,就像您在“我可以让代码通过”代码块中所做的那样。如果我们想要删除不需要的属性并保持其类型安全,我们需要禁止不良的边缘情况。我们保持 T
含糊不清,但声明我们的 Component
不能通过使用 Omit
.
function <T>(Component: React.ComponentType<Omit<T, 'shouldRedirect'>>)
输入 hoistStatics
hoistStatics
是一个接受 HOC 的函数,returns 是一个 HOC。我们知道 HOC 可以操纵 props,使得组合组件 (Outer
) 的 props 不同于基础组件 (Inner
) 的 props。所以我们可以在这里使用两个泛型。无论这两种类型是什么,hoistStatics
都不应该更改它们并且应该 return 一个与其参数具有相同签名的 HOC。
我们定义了一个通用的 HOC
签名来保持干净
type HOC<Inner, Outer = Inner> = (
Component: React.ComponentType<Inner>,
) => React.ComponentType<Outer>
然后我们用那个类型来描述hoistStatics
const hoistStatics = <I, O>(
higherOrderComponent: HOC<I, O>
): HOC<I, O> => (BaseComponent: React.ComponentType<I>) => {
const NewComponent = higherOrderComponent(BaseComponent);
hoistNonReactStatics(NewComponent, BaseComponent);
return NewComponent;
};