为什么TypeScript仍然认为我的变量赋值可能为null

时间:2019-05-23 11:47:36

标签: typescript

我正在尝试使用具有可空成员的类型来扩展成员永远不会为空的另一种类型。

如果我这样做:

type Foo = {
    a: string | null
    b: string | null
}

type Bar = {
    a: string
    b: string
    c: string
}

const foo: Foo = {
    a: 'A',
    b: 'B',
}

const bar: Bar = {
    ...foo,
    c: 'C',
}

TypeScript会抱怨,并告诉我'bar变量'不能为null类型的字符串分配类型null。说“ a”和“ b”可能为空。

我会认为,由于赋值'foo'也没有空值,因此bar赋值也不能有任何空值。

在我的用例中,“ foo”和“ bar”都是测试的一部分,我正在测试foo属性不为null的路径。我可以删除“ Foo”注释,这可以工作,但是在编写测​​试时需要编辑器的帮助。另外,如果以后再添加“ Foo”或“ Bar”类型,我希望打字稿告诉我我的测试已编译,而不是测试失败或失败。这样的事情有可能发生吗,还是我会以错误的方式解决?

3 个答案:

答案 0 :(得分:1)

我认为编译器不是在查看分配,而是在查看类型声明。

另一方面,您通过使用...foo来混合苹果和橙子。我会这样重写,作为副作用,它还可以消除您遇到的错误:

interface Foo {
    a: string | null
    b: string | null
}

interface Bar extends Foo {
    c: string
}

const foo: Foo = {
    a: 'A',
    b: 'B',
}

const bar: Bar = {
   ...foo,
   c: 'C',
}

但是,另一方面,如果FooBar根本不相关,而您仅出于智能需要,那么我会保留您已有的内容,然后添加{{1} }放在...foo as any中。

答案 1 :(得分:1)

我建议您引入一个辅助函数,以验证foo的类型为Foo而不将其扩展为Foo并忽略其属性的非空性。示例:

type Foo = {
  a: string | null;
  b: string | null;
};

type Bar = {
  a: string;
  b: string;
  c: string;
};

// helper function
const verifyType = <T>() => <U extends T>(u: U) => u;

const foo = verifyType<Foo>()({
  a: "A",
  b: "B"
});

const bar: Bar = {
  ...foo, // no error now
  c: "C"
};

调用verifyType<Foo>()返回一个函数,该函数采用类型为U的一个参数,该参数必须可分配给 Foo,并以U的形式返回(而不将其扩展到Foo)。如果该参数不可分配给Foo,则会出现错误:

const fooBad = verifyType<Foo>()({
  a: 1, // error! number not assignable to string | null
  b: "B"
});

请注意,verifyType函数绕过了excess property checks以获得新鲜的对象文字,因此它将允许额外的属性:

const fooAllowExtraProp = verifyType<Foo>()({
  a: "A",
  b: "B",
  x: "extra" // no error
});

如果您要禁止使用其他属性,则可以通过强制U扩展Foo & Record<Exclude<keyof U, keyof Foo>, never>来更改helper函数,使其对参数的接受程度降低,这意味着U可以包含任何其他属性键(Exclude<keyof U, keyof Foo>不为空),这些键的属性必须为never类型(这将排除您向其抛出的任何东西):

const verifyExactType = <T>() => <
  U extends T & Record<Exclude<keyof U, keyof T>, never>
>(
  u: U
) => u;

const fooForbidExtraProp = verifyExactType<Foo>()({
  a: "A",
  b: "B",
  x: "extra" // error!, string not assignable to never
});

好的,希望能有所帮助;祝好运!

Link to code

答案 2 :(得分:0)

因为您明确告诉他foo的类型为Foo。您必须删除类型注释,以便打字稿能够推断出类型。

type Foo = {a: string | null, b: string | null}视为基本类型。 {a: 'A', b: 'B'}的推断类型为{a: 'A', b: 'B'}(没有错字,实际上是类型)。这隐式地实现了类型Foo。 通过使用const foo:Foo = ...,您告诉Typescript右侧必须实现Foo-它可以是其任何实现(隐式或显式)。