TypeScript 入门 | 青训营笔记

55 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的的第14天

什么是TypeScript

TypeScript 发展历史

  • 2012-10 : 微软发布了 TypeScript 第一个版本 (0.8)
  • 2014-10 : Angular 发布了基于 TypeScript 的 2.0 版本
  • 2015-04 : 微软发布了 Visual Studio Code
  • 2016-05 : @types/react 发布,TypeScript 可开发 React
  • 2020-09 : Vue 发布了 3.0 版本,官方支持 TypeScript
  • 2021-11 : V4.5 版本发布

为什么要选择 TypeScript

JS 与 TS 比较

JS:

  • 动态类型
    • 运行期间才做数据类型检查
  • 弱类型语言
    • 类型不安全的语言,数据类型可以被忽略的语言

TS:

  • 静态类型
    • 编译期间做检查数据类型
  • 弱类型语言

TypeScript 优点

  • 可读性增强 : 基于语法解析 TSDoc,ide 增强
  • 可维护性增强 : 在编译阶段暴露大部分错误 -> 多人合作的大型项目中,获得更好的稳定性和开发效率
  • JS 的超集
    • 包含于兼容所有 JS 特性,支持共存
    • 支持渐进式引入与升级

基本语法

基础数据类型

JS

/* 字符串 */
const q = 'string';
/* 数字 */
const w = 1;
/* 布尔值 */
const e = true;
/* null */
const r = null;
/* undefined */
const t = undefined;

TS

/* 字符串 */
const q: string = 'string';
/* 数字 */
const w: number = 1;
/* 布尔值 */
const e: boolean = true;
/* null */
const r: null = null;
/* undefined */
const t: undefined = undefined;

对象类型

const bytedancer: IBytedancer = {
  jobId: 9303245,
  name: 'Lin',
  sex: 'man',
  age: 28,
  hobby: 'swimming',
}

interface IBytedancer {
  /* 只读属性: 约束属性不可在对象初始化外赋值 */
  readonly jobId: number;
  name: string;
  sex: 'man' | 'woman' | 'other';
  age: number;
  /* 可选属性: 定义该属性可以不存在 */
  hobby?: string;
  /* 任意属性: 约束所有对象属性都必须是该属性的子类型 */
  [key: string]: any;
}
/* 报错: 无法分配到 "jobId", 因为它是只读属性 */
bytedancer.jobId = 12345;
/* 成功: 任意属性标注下可以添加任意属性 */
bytedancer.plateform = 'data';
/* 报错: 缺少属性 "name", hobby可缺省 */
const bytedancer2: IBytedancer = {
  jobId: 89757,
  sex: 'woman',
  age: 18,
}

函数类型

相加相乘函数:

function add(x, y) {
  return x + y;
}
const mult = (x, Y) => x * y;

为函数声明类型:

为函数的入参出参补充类型

function add(x: number, y: number): number {
  return x + y;
}
const mult: (x: number, y: number) => number = (x, y) => x * y;

定义接口类型:

interface IMult {
  (x: number, y: number): number;
}
const mult: IMult = (x, y) => x * y;

函数重载

/* 对getDate函数进行重载, timestamp为可缺省参数 */
function getDate(type: 'string', timestamp?: string): string;
function getDate(type: 'date', timestamp?: string): Date;
function getDate(type: 'string' | 'date', timestamp?: string): Date | string {
  const date = new Date(timestamp);
  return type === 'string' ? date.toLocaleString() : date;
}

const x = getDate('date'); // x: Date
const y = getDate('string', '2018-01-10'); // y: string
interface IGetDate {
  (type: 'string', timestamp?: string): string;  
  (type: 'date', timestamp?: string): Date;
  (type: 'string' | 'date', timestamp?: string): Date | string;
}
/* 不能将类型"(type: any, timestamp: any) => string | Date"分配给类型"IGetDate”。
   不能将类型"string | Date"分配给类型"string"。
   不能将类型"Date"分配给类型"string"。ts(2322) */
const getDate2: IGetDate = (type, timestamp) => {
  const date = new Date(timestamp);
  return type === 'string' ? date.toLocaleString() : date;
}

数组类型

/*「类型 + 方括号」表示 */
type IArr1 = number[];
/* 泛型表示 */
type IArr2 = Array<string | number | Record<string,number>>;
/* 元祖表示 */
type IArr3 = [number, number, string, string];
/* 接口表示 */
interface IArr4 {
  [key: number]: any;
}

const arr1: IArr1 = [1, 2, 3, 4, 5, 6];
const arr2: IArr2 = [1, 2, '3', '4', { a: 1 }];
const arr3: IArr3 = [1, 2, '3', '4'];
const arr4: IArr4 = ['string', () => null, {}, []];

TypeScript 补充类型

/* 空类型,表示无赋值 */
type IEmptyFunction = () => void;
/* 任意类型,是所有类型的子类型 */
type IAnyType = any;
/* 枚举类型: 支持枚举值到枚举名的正、反向映射 */
enum EnumExample {
  add = '+',
  mult = '*',
}
EnumExample['add'] === '+';
EnumExample['+'] === 'add';

enum ECorlor { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
ECorlor['Mon'] === 0;
ECorlor[0] === 'Mon';
/* 泛型 */
type INumArr = Array<number>;

TypeScript 泛型

function getRepeatArr(target) {
  return new Array (100).fill(target);
}

type IGetRepeatArr = (target: any) => any [];

/* 泛型特征:不预先指定具体的类型,而在使用的时候再指定类型的一种特性 */
type IGetRepeatArrR = <T>(target: T) => T[];
/* 泛型接口 & 多泛型 */
interface IX<T, U> {
  key: T;
  val: U;
}
/* 泛型类 */
class IMan<T> {
  instance: T;
}
/* 泛型别名 */
type ITypeArr<T> = Array<T>;
/* 泛型约束: 限制泛型必须符合字符串 */
type IGetRepeatStringArr = <T extends string>(target: T) => T[];
const getStrArr: IGetRepeatStringArr = target => new Array(100).fill(target);
/* 报错: 类型"number"的参数不能赋给类型"string"的参数 */
getStrArr(123);

/* 泛型参数默认类型 */
type IGetRepeatArr<T = number> = ( target: T) => T[];
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);
/* 报错: 类型"string”的参数不能赋给类型"number"的参数 */
getRepeatArr('123');

类型别名 & 类型断言

/* 通过type关键字定义了IObjArr的别名类型 */
type IObjArr = Array<{
  key: string;
  [objKey: string] : any;
}>

function keyBy<T extends IObjArr>(objArr: Array<T>) {
  /* 未指定类型时,result类型为 {} */
  const result = objArr.reduce((res, val, key) => {
    res[key] = val;
    return res;
  }, {});
  /* 通过as关键字,断言result类型为正确类型 */
  return result as Record<string, T>;
}

字符串/数字 字面量

/* 允许指定字符串/数字必须的固定值 */

/* IDomTag必须为html、body、div、span中的其一 */
type IDomTag = 'html' | 'body' | 'div' | 'span';
/* IOddNumber必须为1、3、5、7、9中的其一 */
type IOddNumber = 1 | 3 | 5 | 7 | 9;

高级类型

联合/交叉类型

demo

  • 为书籍列表编写类型:
const bookList = [{
  author: 'xiaoming',
  type: 'history',
  range: '2001 - 2021,
}, {
  author: 'xiaoli',
  type: 'story',
  theme: 'love',
}]
  • 类型声明繁琐,存在较多重复
interface IHistoryBook {
  author: string;
  type: string;
  range: string
}
interface IStory Book {
  author: string;
  type: string;
  theme: string;
}
type IBookList = Array<IHistoryBook | IStoryBook>;
  • 联合类型: IA | IB; 联合类型表示一个值可以是几种类型之一
  • 交叉类型: IA & IB; 多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
type IBookList = Array<{
  author: string;
} & ({
  type: 'history';
  range: string;
} | {
  type: 'story';
  theme: string;
})>

类型保护与类型守卫

interface IA { a: 1, al: 2 }
interface IB { b: 1,b1: 2 }

function log (arg: IA | IB) {
  /* JS运行没有问题 */
  /* TS运行报错: */
  /* 报错: 类型"IA | IB"上不存在属性"a"。类型"IB"上不存在属性"a" */
  /* 结论: 访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分 */
  if (arg.a) {
    console.log(arg.a1)
  } else {
    console.log(arg.b1);
  }
}
  • 改造:类型守卫
    • 当两个类型没有任何重复点时
interface IA { a: 1, al: 2 }
interface IB { b: 1, b1: 2 }

/* 类型守卫:定义一个函数,它的返回值是一个类型谓词,生效范围为子作用域 */
function getIsIA( arg: IA | IB): arg is IA {
  /* 存在a时一定为IA类型 */
  return !! ( arg as IA ).a;
}

function log2(arg: IA | IB) {
  if (getIsIA(arg)) { 
    console.log(arg.a1)
  } else {
    console.log(arg.b1);
  }
}
  • 改造:类型保护
//实现函数reverse
//其可将数组或字符串进行反转
function reverse(target: string | Array<any>) {
  /* typof类型保护 */
  if (typeof target === 'string') {
    return target.split ('').reverse().join('');
  }
  /* instance类型保护 */
  if (target instanceof Object) {
    return target.reverse();
  }
}

demo:

//实现函数logBook类型
//函数接受书本类型,并logger出相关特征
function logBook(book: IBookItem) {
  //联合类型 + 类型保护 = 自动类型推断
  if (book.type === 'history') {
    console.log(book.range);
  } else {
    console.log(book.theme);
  }
}

函数返回值类型

demo:

//实现函数delayCall的类型声明
//delayCall接受一个函数作为入参, 其实现延迟1s运行函数
//其返回promise, 结果为入参函数的返回结果

function delayCall(func) {
  return new Promise(resolve => {
    setTimeout(() => {
      const result = func();
      resolve(result);
    }, 1000);
  });
}
type IDelayCall = <T extends() => any>(func: T) => ReturnType<T>;

type IReturnType<T extends (...args: any) => any> = T extends (...args: any)
=> infer R ? R: any

//关键字【extends】跟随泛型出现时,表示类型推断,其表达可类比三元表达式
//如T===判断类型?类型A : 类型B

//关键字【infer】出现在类型推荐中,表示定义类型变量,可以用于指代类型
//如该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中

工程应用

浏览器Web

webpack

在工程中,令 webpack 工程支持 TS:

  1. 配置 webapack loader 相关配置
  2. 配置 tsconfig.js 文件
  3. 运行 webpack 启动 / 打包
  4. loader 处理 ts 文件时,会进行编译与类型检查

NodeJs

使用 TSC 编译

  1. 安装 Node 与 npm
  2. 使用 npm 安装 tsc
  3. 配置 tsconfig.js 文件
  4. 使用 tsc 运行编译得到 js 文件