有没有办法显式地改变TypeScript中变量的已建立控制流类型?

slmsl1lt  于 2022-11-18  发布在  TypeScript
关注(0)|答案(1)|浏览(135)

我知道我们可以使用as关键字在需要的地方内联Assert类型。但是我在这里要做的是改变代码块中变量的类型,使该类型在代码块退出之前一直存在。我希望在不创建单独的变量的情况下实现这一点,理想情况下不需要运行时Assert。

要求摘要

  • as关键字类型Assert
  • 不创建单独的变量
  • 无运行时Assert

示例

我尝试写的是一个setProps方法,它通过一个函数来设置对象的属性。该函数的类型是通用的,这样就强制了正确的prop-to-value类型。函数内部是一个很大的switch语句,它分别处理每个属性,每个属性可能需要多次访问值(因此我不想做asAssert,因为它需要重复)。
更理想的情况是,我希望TypeScript隐式地推断switch语句中的值的类型,但我认为这在今天是不可能的。
下面是一个简单的例子,说明我正在努力实现的目标:

interface Props {
  name: string;
  age: number;
  enrolled: boolean;
}

const props: Props = {
  name: '',
  age: 0,
  enrolled: false,
};

export function setProp<K extends keyof Props>(prop: K, value: Props[K]): void {
  const propName: keyof Props = prop; // This is needed because TypeScript can't break down the union within type K
  switch (propName) {
    case 'name':
      props.name = value;
      // ^-- Error!
      // Type 'string | number | boolean' is not assignable to type 'string'.
      //   Type 'number' is not assignable to type 'string'.(2322)
      break;
    case 'age':
      props.age = value;
      // ^-- Same error!
      break;
    case 'enrolled':
      props.enrolled = value;
      // ^-- Same error!
      break;
  }
}

// Prop name and value type combination are enforced by TypeScript
setProp('name', 'John');
setProp('age', 20);
setProp('enrolled', true);

Playground

关闭解决方案

我想到的最接近的解决方案是使用一个完全未经检查的运行时Assert,并依靠目前许多捆绑器中存在的树抖动/死代码消除来删除它们:

export function uncheckedAssert<T>(value: unknown): asserts value is T {
  return;
}

然后可以将函数重写为:

export function setProp<K extends keyof Props>(prop: K, value: Props[K]): void {
  const propName: keyof Props = prop; // This is needed because TypeScript can't break down the union within type K
  switch (propName) {
    case 'name':
      uncheckedAssert<Props[typeof propName]>(value);
      props.name = value;
      // ^-- No error!
      break;
    case 'age':
      uncheckedAssert<Props[typeof propName]>(value);
      props.age = value;
      // ^-- No error!
      break;
    case 'enrolled':
      uncheckedAssert<Props[typeof propName]>(value);
      props.enrolled = value;
      // ^-- No error!
      break;
  }
}

Playground

7lrncoxx

7lrncoxx1#

从TypeScript 4.9开始,没有内置的类型Assert运算符可以按照你想要的方式在块作用域级别运行。请参见microsoft/TypeScript#10421了解相关的特性请求。解决这个问题的方法几乎是你已经找到的。
Assert函数基本上涵盖了能够以这种方式缩小某个变量的表观类型的用例。
如果能够显式地Assert变量的控制流类型,而不是必须在每个引用上强制转换,那肯定是很好的。调用asserts函数 * 可以 * 获得同样的效果,但当然,该调用在发出的代码中结束。尽管如此,JavaScript VM在减少调用空函数的开销方面还是相当不错的。
大多数的请求都是作为ms/TS#10421的副本关闭的,并说明Assert函数是首选的解决方案。所以我想说在不久的将来这里不太可能有任何改变。不过,任何有兴趣看到这个的人可能会想给予这个问题一个评论👍和/或留下一个评论,描述他们的用例以及为什么Assert函数不能满足需要。

相关问题