TypeScript类型转换

时间:2019-10-15 10:19:10

标签: typescript typescript-typings

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

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,而无需引入另一个变量?

这里是online playground

3 个答案:

答案 0 :(得分:2)

1。联合类型方法

我想您要做的是确定一些财产关系。因为获取不同的类型并强制转换为另一种类型是错误的。我认为您的Item类型在某些情况下可以具有一些其他属性,我们应该在类型定义中进行建模!

让我们从正确键入开始:

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

我创建了Item作为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 ItemNoFoo,而 out 类型是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中定义),它将确定值是这两种类型中的哪一种。

答案 1 :(得分:1)

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);

Try it在线

答案 2 :(得分:1)

1。)为什么obj = obj as WithFoo不起作用?有什么方法可以在不引入其他变量的情况下强制转换类型?

首先,将obj函数参数变量声明为Item类型,因此TS不知道obj包含类型{ foo?: string}。您的第二个机会是编译器的分配control flow analysis,在这种情况下,由于obj没有联合类型,因此无法将WithFoo类型缩小为obj

  

将类型S的值的赋值(包括声明中的初始化程序)分配给类型T的变量,将在赋值之后的代码路径中将该变量的类型更改为T并以S缩小。

     

以T缩小的类型T的计算如下:

     

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

这就是为什么在示例中出现错误Property 'foo' does not exist on type 'Item'的原因。相反,following code会适当缩小并编译:

type WithFoo = { foo?: string; }

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

如果您不想进行重新分配或引入其他变量,则可以使用内联type assertion来访问foo属性,该属性通常可在所有JavaScript expressions上使用:

(obj as WithFoo).foo = 'hello';

话虽如此,一种更安全的方法可能是假设obj为联合类型Item | WithFoo并使用type guards而不是强制转换(请参阅@Maciej Sikora的答案)。

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

声明诸如const anotherObj = obj as AnotherType之类的新变量时,编译器会自动将变量anotherObj的类型推断为AnotherType。 TS会进行其他检查,以确保AnotherTypetypeof obj兼容。例如。 this无法编译:

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

3。)声明变量后可以更改其类型吗?

不是,letconst变量cannot be redeclaredvar具有相同的类型,但在这里并不重要),这意味着声明的变量类型也不能更改。不过,可以通过控制流分析来缩小变量,请参见1。)。