reactjs - 在 React+Typescript 中合并两种类型
问题描述
我想使用组件继承,允许抽象类中的公共状态和继承者中的其他状态。如何正确组合这两种状态?我试图省略一个外部泛型,但是当我尝试设置状态时仍然出现类型错误。
我的代码示例:
import * as React from "react";
type CoreState = {
coreState1: number;
coreState2: string;
};
abstract class CoreComponent<OuterState = {}> extends React.Component<{}, Omit<OuterState, keyof CoreState> & CoreState> {
componentDidMount() {
this.setState({
coreState1: 1,
coreState2: "test",
});
}
}
type BaseState = {
state1: number;
state2: string;
};
class AnotherComponent<OuterState = {}> extends CoreComponent<Omit<OuterState, keyof BaseState> & BaseState> {
componentDidMount() {
super.componentDidMount();
this.setState({
state1: 1,
state2: "test",
});
}
render() {
return null;
}
}
我得到的错误:
TS2345: Argument of type '{ state1: 1; state2: "test"; }' is not assignable to parameter of type '(Pick<Pick<OuterState, Exclude<keyof OuterState, "state1" | "state2">> & BaseState, "state1" | "state2" | Exclude<Exclude<keyof OuterState, "state1" | "state2">, "coreState1" | "coreState2">> & CoreState) | ((prevState: Readonly<...>, props: Readonly<...>) => (Pick<...> & CoreState) | ... 1 more ... | null) | Pick<....'.
Types of property 'state1' are incompatible.
Type '1' is not assignable to type '(Pick<OuterState, Exclude<keyof OuterState, "state1" | "state2">> & BaseState)["state1"] | (Pick<Pick<OuterState, Exclude<...>> & BaseState, "state1" | ... 1 more ... | Exclude<...>> & CoreState)["state1"] | undefined'.
解决方案
不幸的是,编译器无法对未指定的泛型类型执行高阶类型分析,以得出结论您正在做的事情是类型安全的。具体来说,它需要能够为所有T extends object
和验证以下内容U extends object
:
U extends Pick<Omit<T, keyof U> & U, keyof U> //
当是未指定的泛型类型时,它不能这样做T
,例如OuterState
在CoreComponent<OuterState>
. 因此有一个问题this.setState({ coreState1: 1, coreState2: "test" })
; 如果你替换OuterState
和T
,CoreState
你U
会得到有问题的比较。
我能在 GitHub 中找到最接近规范问题的限制是microsoft/TypeScript#28884。如果您认为它足够引人注目,您可能想也可能不想解决这个问题并给出它或解释您的用例。但目前尚不清楚这是否会得到解决,或者何时会发生。现在,接受这是 TypeScript 的限制是有意义的。
因此,一旦您发现自己对未指定的泛型类型进行类型操作,您可能需要使用类型断言等来克服编译器高阶类型分析的缺陷。
例如,您可以这样做:
type Merge<T, U> = Omit<T, keyof U> & U;
type PickMerge<T, U> = Pick<Merge<T, U>, keyof U>;
abstract class CoreComponent<S = {}> extends React.Component<{}, Merge<S, CoreState>> {
componentDidMount() {
this.setState({
coreState1: 1,
coreState2: "test",
} as PickMerge<S, CoreState>); // assert here
}
}
我已经创建了Merge
,PickMerge
因此您不必重复自己。类型断言只是告诉编译器我们确定对象字面量是类型的PickMerge<S, CoreState>
,它不应该担心验证它。相似地:
class AnotherComponent<S = {}> extends CoreComponent<Merge<S, BaseState>> {
componentDidMount() {
super.componentDidMount();
this.setState({
state1: 1,
state2: "test",
} as PickMerge<Merge<S, CoreState>, BaseState>); // assert here
}
}
您可以看到这是可行的,尽管它开始变得乏味。你总是可以说as any
并放弃更多的类型安全以换取方便。
另一种可能性是某种重构,无论是发出的 JS 还是类型,但这在很大程度上取决于您的用例。例如,您可以说组件的状态是 and 的联合,这样编译器很乐意允许指定 a :CoreState
Merge<S, CoreState>
CoreState
abstract class CoreComponentAlternate<S = {}> extends
React.Component<{}, CoreState | Merge<S, CoreState>> {
componentDidMount() {
this.setState({
coreState1: 1,
coreState2: "test",
});
}
}
但当然,这可能与您的其他代码库交互不佳。如果是这样,断言可能是您能够实现的最好的。
推荐阅读
- c# - OpenXML - 如何将值插入特定工作表?
- go - 我如何初始化切片以供将来分配?
- python - 将for循环中的numpy数组导出到Python中的一个CSV
- java - Kafka 集群显示连续日志“INFO [SocketServer] Failed authentication (SSL handshake failed) (org.apache.kafka.common.network.Selector)”
- ansible - Ansible:部署 VM(Linux 和 Windows)的剧本
- javascript - 如何更改 jquery datetimepicker 的外观,添加按钮和自定义箭头:
- c - Linux 上的 C 程序中的无效输出
- flutter - Flutter - 为什么调试器在函数完全调试之前调试函数调用下的代码?
- sql - 为什么提交会触发访问冲突?
- django - 尝试在序列化程序“UserViewSerializer”上获取字段“email”的值时出现 AttributeError