TypeScript类型转换

4

我有一个对象,在某些情况下需要添加一些额外的属性。

interface Item {
    name: string
}

function addProp(obj: Item) {
    type WithFoo = Item & { foo?: string; }

    // The following does NOT work
    obj = obj as WithFoo;

    if (obj.name == 'test') {
        obj.foo = 'hello';
     }
}


看起来 obj = obj as AnotherType 不起作用,但如果我赋值给另一个变量,则有效,const anotherObj = obj as AnotherType

有没有一种方法可以强制转换 type而不需要引入另一个变量?

这是在线 playground

3个回答

6
1.) 为什么obj = obj as WithFoo不起作用?有没有办法在不引入另一个变量的情况下进行类型转换? 首先,函数参数变量obj被声明为Item类型,因此TS不知道obj包含{ foo?: string }类型。您的第二个机会是编译器的控制流分析,针对赋值,在这种情况下,无法将obj类型缩小为WithFoo,因为obj没有联合类型:

将类型为S的值(包括声明中的初始值)分配给类型为T的变量会更改该变量的类型为在分配后的代码路径中由S缩小的T。

由S缩小的T的类型计算如下:

如果T不是联合类型,则结果为T。
如果T是联合类型,则结果是每个T中可分配给S的组成类型的联合。

这就是为什么在示例中会出现错误Property 'foo' does not exist on type 'Item'的原因。相反,以下代码将正确缩小并编译:
type WithFoo = { foo?: string; }

function addProp(obj: Item | WithFoo) {
    obj = obj as WithFoo;
    obj.foo = 'hello';
}

如果您不想进行重新赋值或引入另一个变量,foo属性可以通过内联类型断言来访问,这通常可用于所有JavaScript表达式
(obj as WithFoo).foo = 'hello';

话虽如此,更安全的方式可能是假设 obj 是一个联合类型 Item | WithFoo 并使用类型保护而不是强制类型转换(见@Maciej Sikora的回答)。

2.) 为什么const anotherObj = obj as AnotherType 能起作用?

当你声明一个新变量像 const anotherObj = obj as AnotherType,编译器会自动推断变量 anotherObj 的类型为 AnotherType。TS 还会进行额外的检查,以确保 AnotherTypetypeof obj 兼容。例如,这个例子就不能编译:

function addProp(obj: Item) {
    const anotherObj = obj as string // error (OK)
    // ...
}

3.) 变量声明后能否改变其类型?

不行,letconst 变量不能被重新声明var 可以使用相同的类型进行重新声明,但这里并不重要),这意味着,已经声明的变量类型也无法更改。不过,通过控制流分析可以缩小变量范围,详见1.)。


那么,我们在声明后就不能更改变量的type吗?在我的情况下,objtype将永远是Item,我无法将其type更改为另一个,即使是兼容的type?请参见此playground - Jess
在情况1中,如果我将类型WithFoo更改为type WithFoo = { foo?:string; name:string; },则转换将失败。请参见此播放列表 - Jess
再次查看上面的引用。在您的 playground 中,进行转换后,变量 obj 的类型成为可分配给 Item | WithFoo 中每个组成类型的联合。在您的最后一个示例中,WithFoo 可以分配给 ItemWithFoo,因此结果类型再次为 Item | WithFoo。但是 TypeScript 抱怨,因为联合的所有组成部分中都没有 foo 属性(它可能是 Item,那么您就不能使用 foo)。 - ford04

5

1. 联合类型方法

我认为你想做的是确定一些属性关系。直接获取不同类型并强制转换为另一个类型是错误的。我认为你的 Item 类型在某些情况下可以具有一些附加属性,我们应该在类型定义中对其进行建模!

让我们从正确的类型开始:

type ItemNoFoo = { name: string };
type ItemWithFoo = ItemNoFoo & {foo: string}
type Item = ItemNoFoo | ItemWithFoo;

我将创建一个 ItemNoFooItemWithFoo 的联合体作为项。感谢这样我们可以定义对象的两种不同状态。

现在我将创建一个守卫函数,用于检查我们是处于 ItemNoFoo 还是 ItemWithFoo 状态。

const hasFoo = (item: Item): item is ItemWithFoo => item.name === 'test';

好的,现在我们可以在一个类型的作用域内(因为我们的类型只是两个其他类型的联合)询问 Item 是否拥有属性 foo。

最终代码:

type ItemNoFoo = { name: string };
type ItemWithFoo = ItemNoFoo & {foo: string}
type Item = ItemNoFoo | ItemWithFoo;

const hasFoo = (item: Item): item is ItemWithFoo => item.name === 'test';

function addProp(obj: Item) {
    if (hasFoo(obj)) {
        obj.foo = 'hello'; // in if block obj is inferenced as ItemWithFoo!
     }
}

关于这种方法的更多信息,您可以在此处找到 - Sum types


2. 函数作为数据转换器

如果我们的函数需要创建一个新结构,就像是一个数据创建器或者数据转换器,那么我们应该将其视为一个in -> out管道。在类型中,in类型是ItemNoFooout类型是ItemWithFoo | ItemWithFoo (参见第1点中的类型定义)。

function transform(obj: ItemNoFoo): Item {
    if (obj.name === 'test') {
        return {
          ...obj,
          foo: 'hello'
        } // here we create new value with new type
     } else {
       return obj;
     }
}

正如您所看到的,联合类型Item仍然很方便,因为此函数返回或未更改的ItemNoFoo类型实例或ItemWithFoo实例。

让我们来看一下它的使用:

function f(item: ItemNoFoo) {
  const finalItem = transform(item); // type of finalItem is ItemWithFoo | ItemNoFoo
} 

如果你想确定你正在处理的是哪个值,那么有用的方法可以是 hasFoo 保护函数(在解决方案1中定义),该函数将确定这两种类型中的哪一种。


Item来自于后端REST API,在原始数据中,foo不存在。但是在某些情况下,我需要添加foo属性。因此,我不想改变API请求的返回类型,只想在处理函数中添加foo属性。不过,我想知道是否可以在变量声明后更改其typeobj = obj as ItemWithFoo并不会改变obj的类型,但将其分配给另一个变量可以起作用。 - Jess
@Jess,请检查我添加的第二个选项。 - Maciej Sikora

2
"

obj = obj as WithFoo" 无法工作,因为obj的类型是Item,而你想要存储一个WithFoo类型的对象在其中。

但是你可以使用另一个类型为WithFoo的变量,并将obj存储在其中。因为obj是一个对象,所以这两个变量将保持对相同数据的引用:

interface Item {
    name: string
}

function addProp(obj: Item) {
    type WithFoo = Item & { foo?: string; }
    const foo = obj as WithFoo;
    if (obj.name == 'test') {
        foo.foo = 'hello';
     }
}

const x1: Item = { name: 'test' }
addProp(x1);
console.log(x1);

const x2: Item = { name: 'not-matching' }
addProp(x2);
console.log(x2);

尝试在网上试用

我不明白为什么 obj = obj as WithFoo 不能改变 objtype。或者在 TypeScript 中,我们能否更改现有变量的类型? - Jess
变量的类型是不可改变的。obj as WithFoo 不会改变 obj 或存储在 obj 中的对象的类型。它只是告诉编译器:“相信我,存储在 obj 中的对象遵循 WithFoo 的接口”。这不是类型转换;TypeScript 和 Javascript 都不支持类型转换。 - axiac
变量的类型不能改变。我在TS文档中没有找到相关信息,尽管这对我来说似乎是合理的。 - Jess

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