前言

在学习 typescript 的过程中,我希望将学习的内容记录下来,但是与其将该篇文章作为自己学习的笔记,不如将其写为一种注意问题样子的东西,能让我在后续开发过程中,逐步增加、完善里面的问题,成为一个开发中的小册子。

注意:因为目标是一份类似于注意方面和思考问题的册子,所以本文含有大量的主观臆断,不一定适合所有场景,请酌情参考。

开发注意问题

1. 字符串拼接采用模版字符串

采用模版字符串代替拼接:

let myName: string = 'Tom'

// not good
let sentence: string = "Hello, my name is "+ myName

// this is better
let sentence: string = `Hello, my name is ${myName}`

使用 ES6 中的模板字符串${expr} 用来在模板字符串中嵌入表达式。

2. 对象尽量不要赋空(惰性初始化)

在真正开发过程中,很少会出现对象赋空的情况,请尽量避免赋空。

如果真的在开始初始化的时候,无法直接给值,但是又一定要初始化的时候,也尽量不要直接赋空,这边建议采用惰性初始化。

let foo = {};
foo.bar = 123; // Error: Property 'bar' does not exist on type '{}'
foo.bas = 'Hello World'; // Error: Property 'bas' does not exist on type '{}'

采用 interface 惰性初始化

interface Foo {
  bar: number;
  bas: string;
}

let foo = {} as Foo;
foo.bar = 123;
foo.bas = 'Hello World';

注意:采用 typescript 的类型断言机制可以通过编译,但是这样做其实在绕过类型检查机制,我们不建议采用

et foo = {} as any; foo.bar = 123; foo.bas = 'Hello World';

3. 少用甚至不用Any

尽量在一开始就判断一个数据到底是那种类型,赋值可以在后面赋值,但是声明尽量在初始化的时候就判定好,而不是采用 any 或者类型推断跳过。

// not good
let something: any;
something = 'seven';

// this is better
let something!: string;
something = 'seven';

注意:如果是类似于 DartKotlin 这种语言,对这种初始化并不会开始赋值的情况,会有一种 lateinit 机制,但是这个 TypeScript 暂时没有类似的关键字,类似的就是采用 Strict Class Initialization 方法

4. 少用类型推断(类型推论)

如果一个变量类型不会发生改变,我们尽量初始化就给他定义好属性,这样子后续在赋值类型不对的变量的时候会报错。

let myNumber: string;
myNumber = 'seven';
myNumber = 7;						// this is wrong!

如果这个地方采用类型推断,则不会报错,也不会有提醒,这并不是我们想要的。

注意:如果一个值真的可能会有两种类型,这种情况下可以采用联合类型的方式:

let myNumber: string | number;

但是如果不是强制的,这个地方考虑将这个变量拆分为两个变量是更好的选择。

5. 不应该采用接口定义数组

定义一个数组,应当符合直观、简洁的办法:

// 直接指定
let fibonacci: number[] = [1, 1, 2, 3, 5];
// 采用泛型指定
let fibonacci: Array<number> = [1, 1, 2, 3, 5];

这两个办法都是很好的定义数组的办法,简洁直观。

不建议,也不应该采用接口描述:

// 这并不建议
interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

注意:并非所有情况下,上面两种都可以,有一种情况下(类数组),采用接口描述数组更加好。严格来说,类数组并非数组类型(例如 arguments)。

6. 正确的函数声明

我认为函数包含几个重点:输入、输出。

因此,声明正确的函数应当规范好,输入的规范,和输出的规范:

function sum(x: number, y: number): number {
    return x + y;
}

如果输出的是一个对象,建议采用 interface 去定义返回的类型。

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

function input(name: string, age: number): person {
  return { name, age };
}

7. 慎用类型断言( as )

类型断言有些时候很好用,使用 as any 在大多数情况下能让编译器通过我们的代码,这很便利。

但是需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误。因此使用断言前考虑以下几点:

  • 该类型后续是否会调用方法,或者深层次的属性引用,如果有,请慎用。
  • as any 很有可能掩盖了真正的错误,所以如果不是非常确定,就不要使用 as any
  • 类型断言并非是类型转换,它并非会真正影响变量的真正类型,如果想要转换,请采用类型转换方法。
  • 在该场景下,是否可以采用泛型,如果可以采用泛型,请尽量不要采用断言。

8. 为每个组件Props添加interface

只要是组件,并且符合复用的情况的话,都应该为 props 添加 interface ,简单的可以直接采用如下方式:

export default (props: { title: string; certType: string; certInstitution: string }) => {
  //.....

如果遇到复杂情况,或者含有嵌套关系,建议写明 interface 并做好注释标记。

interface wrapperProps {
  // 组件名字
  title: string,
  // 子组件渲染需要的参数
  renderData: {
    data: Date,
    loaction: string,
    unit: string,
  }
  // 请求回调
  success?: () => void
}

const WrapperComponet = (props: wrapperProps)=>{
  // .......