typescript - 如何防止 TypeScript 中的文字类型

标签 typescript typescript-generics

假设我有一个这样的类,它包含一个值:

class Data<T> {
  constructor(public val: T){}

  set(newVal: T) {
    this.val = newVal;
  }
}

const a = new Data('hello');
a.set('world');
// typeof a --> Primitive<string>

到目前为止一切顺利,但现在我想将它限制为一组类型中的一个,让我们说原语:
type Primitives = boolean|string|number|null|undefined;

class PrimitiveData<T extends Primitives> {
  constructor(public val: T){}

  set(newVal: T) {
    this.val = newVal;
  }
}

const b = new PrimitiveData('hello');
b.set('world'); // Error :(

Playground link

最后一行失败,因为 bPrimitive<'hello'>不是 Primitive<string> ,等等 set只会采用文字 'hello'作为一种值(value),这显然不是我所追求的。

我在这里做错了什么?没有我自己明确地扩大类型(例如: new Primitive<string>('hello') )有什么我可以做的吗?

最佳答案

typescript intentionally infers literal types just about everywhere ,但通常会扩大这些类型,除非在少数情况下。一种是当您有一个类型参数时 extends加宽类型之一。启发式是,如果您要求 T extends string您可能希望保留确切的文字。工会仍然如此,例如 T extends Primitives ,所以你会得到这种行为。

我们可以使用 conditional types强制(联合)字符串,数字和 bool 文字扩大到(联合)string , number , 和 boolean :

type WidenLiterals<T> = 
  T extends boolean ? boolean :
  T extends string ? string : 
  T extends number ? number : 
  T;

type WString = WidenLiterals<"hello"> // string
type WNumber = WidenLiterals<123> // number
type WBooleanOrUndefined = WidenLiterals<true | undefined> // boolean | undefined

现在这很好,您可能想要继续的一种方法是使用 WidenLiterals<T>代替 T里面到处都是PrimitiveData :
class PrimitiveDataTest<T extends Primitives> {
  constructor(public val: WidenLiterals<T>){}
  set(newVal: WidenLiterals<T>) {
    this.val = newVal;
  }
}

const bTest = new PrimitiveDataTest("hello"); // PrimitiveDataTest<"hello">
bTest.set("world"); // okay

就目前而言,这有效。 bTest类型为 PrimitiveDataTest<"hello"> ,但实际类型为valstring你可以这样使用它。不幸的是,您会遇到这种不良行为:
let aTest = new PrimitiveDataTest("goodbye"); // PrimitiveDataTest<"goodbye">
aTest = bTest; // error! 
// PrimitiveDataTest<"hello"> not assignable to PrimitiveDataTest<"goodbye">.
// Type '"hello"' is not assignable to type '"goodbye"'.

这似乎是由于 bug在 TypeScript 中,条件类型没有被正确检查。类型 PrimitiveDataTest<"hello">PrimitiveDataTest<"goodbye">都是structurally identical to彼此和到PrimitiveDataTest<string> ,所以类型应该是可以相互分配的。它们不是一个错误,在不久的将来可能会或可能不会得到解决(也许为 TS3.5 或 TS3.6 设置了一些修复?)

如果那没问题,那么你可能就到此为止了。

否则,您可能会考虑使用此实现。定义一个不受约束的版本,如 Data<T> :
class Data<T> {
  constructor(public val: T) {}
  set(newVal: T) {
    this.val = newVal;
  }
}

然后定义类型和值PrimitiveDataData 相关像这样:
interface PrimitiveData<T extends Primitives> extends Data<T> {}
const PrimitiveData = Data as new <T extends Primitives>(
  val: T
) => PrimitiveData<WidenLiterals<T>>;

名为 PrimitiveData 的类型和值对就像一个泛型类,其中 T被限制为 Primitives ,但是当您调用构造函数时,生成的实例是加宽类型的:
const b = new PrimitiveData("hello"); // PrimitiveData<string>
b.set("world"); // okay
let a = new PrimitiveData("goodbye"); // PrimitiveData<string>
a = b; // okay

这对 PrimitiveData 的用户来说可能更容易一起工作,虽然执行 PrimitiveData确实需要一些跳箍。

好的,希望能帮助你前进。祝你好运!

Link to code

关于typescript - 如何防止 TypeScript 中的文字类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56332310/

相关文章:

javascript - "TS2339: Property ... does not exist on type ...", 在输入字段

typescript - 调试 TypeScript AWS CDK 应用程序

javascript - 在 lambda 表达式与 Typescript 函数中使用关键字 this 时会发生什么

javascript - TypeScript 从现有接口(interface)创建新接口(interface)并更改所有属性的类型

TypeScript 通用默认类型与上下文类型

Typescript:使用函数数组的 ReturnType(已编辑)

javascript - Angular 7 ngx-translate 不记得刷新后的语言

javascript - 如何通过 Angular 中的异步 base64 字符串显示图像

typescript :返回 `instance type` 的 `typeof class`

typescript - 在 Typescript 中,如何编写一个柯里化(Currying)函数来限制并从第一个参数推断输出类型?