TypeScript部分接口对象

25
在React中,组件定义大致如下:
class Component<S> {
    state:S;
    setState(state:S):void;
}

你可以像这样定义组件:

interface MyState {
    name: string;
    age: number;
}
class MyComponent extends Component<MyState> { }

我现在遇到的问题是,在React API中,setState应该使用一个部分状态对象进行调用,代表要更新的属性,例如:

setState({name: "Aaron"});
请注意,age未被声明。 我面临的问题是TypeScript不允许这样做,它会给出类似于属性“age”缺失的赋值错误。 就我所知,react.d.ts定义在这方面是错误的。 但是有解决方案吗?
我尝试了这个:
setState({name: "Aaron"} as MyState);

但是即使使用相同的代码,它仍然会产生错误,尽管在在线编辑器里却不会产生错误。为什么在线编辑器可以工作而本地构建出现错误?有任何想法吗?


我不知怎么做到的,使用Object.assign部分类型可以工作(完全具备类型检查功能,而且无需创建带有可选成员的接口声明),但我仍然不确定如何实现。同样的代码在一个项目中可以工作,在另一个项目中却不能。一旦我弄清楚了,我会发布更新的。 - John Weisz
3个回答

28

"局部类型"在TypeScript中目前尚缺失,详见TypeScript问题#4889以及这个相关问题。很遗憾,目前无法实现此类型检查。

您可以考虑将MyState接口的所有字段都标记为可选的(通过添加?修饰符),但这会削弱像Component.state这样的东西的类型检查(在这种情况下,您希望设置全部字段)。

编辑(2016年12月): TypeScript 2.1引入了映射类型,支持使用Partial<T>描述局部类型!现在您可以使用以下类型定义来定义Component

class Component<S> {
    state: S;
    setState(state: Partial<S>) : void;
}

只是为了举例说明给定的解决方法,在您的情况下,您的接口将是:interface MyState { name?: string; age?: number; } - Buzinas
谢谢。事实上,我一直在避免将所有字段标记为可选的,因为这并不是真的...你有什么想法为什么我的 Playground 示例能够工作吗?看起来它不应该这样吧? - Aaron Beall
@Aaron那个例子有效是因为as MyState 充当了类型检查器的覆盖。不过,我不明白相同的代码在你本地构建中为什么会失败... - Mattias Buelens
@MattiasBuelens 这似乎与可选属性有关系。可以在这里看到。 这里有什么区别? - Aaron Beall
@MattiasBuelens 我已接受此答案,但我仍然不理解为什么 interface S {a: string; b: string};let s:S = {a:"hi"} as S; 配合使用正常工作,但如果我将 a 设为可选,例如 interface S { a?: string; b: string},那么在赋值/断言时就会出现缺少 b 属性的错误? - Aaron Beall

6
“react.d.ts” 的定义确实有误,直到支持Partial Types才能进行修正。
问题在于 setState 接受部分状态(partial state),这是一种不同于普通状态的类型。您可以通过手动指定两种类型来解决这个问题:
interface MyState {
    name: string;
    age: number;
}

interface PartialMyState {
    name?: string;
    age?: number;
}

手动声明MyComponent,而不是扩展提供的React Component类:

class MyComponent {
    state: MyState;
    setState(state:PartialMyState): void;
    //...
}

这意味着您需要为代码中的每个Component子类复制这些函数定义。您可以通过定义一个正确的Component类来避免这种情况,该类由一个附加类型的部分状态进行泛化:
class CorrectComponent<S,P> { // generalized over both state and partial state
    state:S;
    setState(state:P):void;
    //...
}

class MyComponent extends CorrectComponent<MyState,PartialMyState> { }

您仍需为每个状态类型编写部分版本。


或者,您可以通过将其参数类型更改为Object来使setState不具有类型安全性。


0

正如其他人所述,自TypeScript 2.1以来,已经实现了Partial关键字。

但是对于未来的读者,值得注意的是在扩展接口和类型时语法上的区别。

对于接口:

interface MyState {
    name: string;
    age: number;
}

interface MyStatePartial extends Partial<MyState> {}
// Results in:
// {
//     name?: string;
//     age?: number;
// }

请注意,末尾空花括号({})的原因是因为我正在扩展接口,但不添加任何新键(请参阅文档)。
对于类型:
type MyState = {
    name: string;
    age: number;
}

type MyStatePartial = Partial<MyState>;

注意:如果您想要与界面版本类似的语法,您可以这样做:
type MyStatePartial = Partial<MyState> & {};

虽然有些冗余,但它确实让读者看到了类型和接口之间的语法相似性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接