Published on

TypeScript使用注意点整理

Reading time
3 分钟
作者

初次接触TypeScript是刚入职的时候要写Angular,当时其实连JavaScript都没写明白,更别说TypeScript了,依葫芦画瓢,硬是写成了"AnyScript"。当时唯一的感受是,这东西好难用,为什么要给自己加这么多限制。

后来新项目选型的时候我选择了Vue2,使用的是JavaScript,这段有点折磨的经历也就这么过去了。时过境迁,在开发过程中,我居然时不时想念起TypeScript,总觉得有时候如果使用的是它而不是JavaScript会更加方便。

但此时的我依然对TypeScript一知半解,只知道基本类型注释怎么使用,于是我下定决心,通读了官网的Handbook,偶尔维护老项目时实践了一下,感觉使用起来更加得心应手,也有了一些新的理解,这里记录一下阅读过程中觉得值得注意的地方,后续会持续更新使用过程中容易踩的坑以及一些使用心得。

一、字面量推断(Literal Inference)

错误示例

在使用字面量类型时,容易出现以下的错误

declare function handleRequest(url: string, method: "GET" | "POST"): void;

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);

上述的代码会报这样一个错误:

Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

为什么?因为req中的method会被推断为string类型而不是GET这个值,在req的创建和handleRequest调用之间,req.method是可以被重新复制的,因此这里会被TypeScript判定为有错误。

如何解决?

  • 方法一: 在以下任意一个位置使用断言,将method的类型固定为Get
// 位置1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// 位置2:
handleRequest(req.url, req.method as "GET");
  • 方法二: 定义req时使用断言,将整个对象转化为字面量类型
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);

二、真值窄化(Truthiness narrowing)

虽然下方两行代码的最终值都是true,但是TypeScript将会推断第一行的类型是Boolean,推断第二行的类型是字面量true

Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true,    value: true

三、解构赋值

使用解构赋值时,可以用以下三种方法设置类型

  • 方法一:
function greet(person: { name: string; age: number }) {
  return "Hello " + person.name;
}
  • 方法二:
interface Person {
  name: string;
  age: number;
}

function greet(person: Person) {
  return "Hello " + person.name;
}
  • 方法三:
type Person = {
  name: string;
  age: number;
};

function greet(person: Person) {
  return "Hello " + person.name;
}

但是需要注意的是,不可以直接在解构的对象内直接设置类型

function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
  render(shape);
}

在JavaScript中,shape: Shape语法意味着获取shape属性的值并将其重命名为Shape,显然TypeScript不能和JavaScript原有语法冲突,因此上述代码会报错: Cannot find name 'shape'. Did you mean 'Shape'?

四、只读属性与只读数组

在TypeScript中可以使用readonly关键字将属性标记为只读

interface SomeType {
  readonly prop: string;
}

function doSomething(obj: SomeType) {
  // 可以读取
  console.log(`prop has the value '${obj.prop}'.`);
  // 不能赋值
  obj.prop = "hello";
  // Cannot assign to 'prop' because it is a read-only property.
}

值得注意的是,TypeScript在检查两种类型是否兼容时将readonly考虑进来

interface Person {
  name: string;
  age: number;
}

interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}

let writablePerson: Person = {
  name: "Person McPersonface",
  age: 42,
};

// 正常运行
let readonlyPerson: ReadonlyPerson = writablePerson;

console.log(readonlyPerson.age); // -> '42'
writablePerson.age++;
console.log(readonlyPerson.age); // -> '43'

这一点和只读数组类型是不同,只读数组和常规数组之间的赋值不是双向的

let x: readonly string[] = [];
let y: string[] = [];

x = y;
y = x;
// The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.