首页 > 解决方案 > 安全地消除打字稿中的无效性

问题描述

我正在寻找有关处理无效值的更好方法的建议。

我目前正在学习打字稿。我经常遇到的问题之一是将可能未定义或为 null 的值转换为既不是未定义也不是 null 的类型值。

例如

    currentTarget?: HTMLEement;

...
    let position = this.currentTarget.styles.position;
    

这当然会产生错误,因为currentTarget可能是undefined. 这是伟大的。我明白了。

通常,人们通过间接方式知道可能为空的表达式不能为空。我的偏好是在这种情况下抛出一个错误,如果表达式实际上是 null,而不是使用 null 合并运算符或保护 if 语句来使错误消失。例如,我可以编写someObject?.someProperty?.someMethod()使错误消失的代码;但它忽略了这样一个事实,即如果这些空合并运算符中的任何一个有效,它几乎总是一个错误。someObject.someProperty.someMethod()编写并抛出异常会更好(尽管不可能) 。

我已经开始编写保护函数来消除无效性。例如:

model?: Model;

getModel(): Model {
    if (!this.model) throw new StateError("Model is null.");
    return this.model;
}

并考虑了以下想法:

function nullCast<T>(value: T | null | undefined): T {
   if (!value) throw new Error("Value is null");
   return value;
}

[编辑:更正了代码nullCast(效果非常好。]

没有什么可怕的

if (!this.model) throw new StateError("Model is null");
... carry on with non-nullish model...

而且我正在逐渐学习为变量提供默认值,即使它们是复杂类型的,而不是将对象声明为可空值。

{
  model: SomeModel.EmptyModel();
     ... instead of ...
  model?: SomeModel;

}

但最后,我的代码中似乎有一个不可忽略的部分正在处理可以为空的变量,我知道我知道这些变量不是空的,因为我所在的函数如果程序不会执行处于这些变量可能具有空值或未定义值的状态。

目前手头有一个引人注目的例子:一个模型类,它通过 websocket 从远程服务器获取初始状态。初始状态是异步填充的,在此期间会显示“正在加载...”屏幕。这反过来又强制实现 UI 的代码处理 null 或未定义的模型属性,如果 UI 正在显示,这些属性不可能为 null。

虽然我很欣赏要完全正确需要付出努力,但我不禁认为我错过了更好的方法来处理这个问题。对替代方案或最佳实践的建议表示赞赏。

标签: typescriptcastingreact-typescript

解决方案


“例如,我可以编写someObject?.someProperty?.someMethod()使错误消失的代码;但它忽略了这样一个事实,即如果任何这些空合并运算符中的任何一个有效,它几乎总是一个错误。”

如果有一个空值存在异常情况,那么您的类型不应允许该空值。听起来你的类型太宽容了。如果值为 时是错误null,那么按理说当值为 时应该是类型错误null

处理 nullish 值是我们都必须处理的事情,但是很难概括,因为在 null 情况下要做什么很大程度上取决于您的应用程序/代码的工作方式。


一般来说,当我有大部分时间需要为非空的属性时,我会这样声明它们。

interface MyType {
  someObject: {
    someProperty: {
      someMethod(): void
    }
  }
}

然后仅在该情况需要时使字段可选:

function makeMyType(values: Partial<MyType>): MyType {
  return { ...defaultMyType, ...values }
}

有时它真的不能轻易避免,在这种情况下,我确实做了很多这样的事情:

if (!foo) throw new Error('really expected foo to be here!')
console.log(foo.bar)

或者在某些情况下只是:

if (!foo) return
console.log(foo.bar)

这很好。如果它真的是无效的,那么你会收到一条关于出了什么问题的很好的错误消息。或者,在第二种情况下,您只需放弃一个在 null 情况下无效的函数。


如果没有您的类型的详细信息,很难更具体地提出建议。但最后如果:

“似乎我的代码中不可忽略的部分正在处理可以为空的变量”

然后我会首先查看您的类型,以确保您尽可能多地消除无效性。


另一种选择是使用类型谓词函数来验证整个对象,然后您可以使用它们而无需在任何地方测试每个属性:

interface Foo {
  foo: string
  bar: string
  baz: string
}

function validateFoo(maybeFoo: Partial<Foo>): maybeFoo is Foo {
  return !!maybeFoo.foo && !!maybeFoo.bar && !!maybeFoo.baz
}

const partialFoo: Partial<Foo> = { bar: 'a,b,c' }
if (validateFoo(partialFoo)) {
  partialFoo.bar.split(',') // works
}

操场


推荐阅读