TypeScript入门 | 青训营笔记

57 阅读7分钟

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

重点内容

  • TypeScript 历史及定义解析
  • TypeScript 优势解读
  • TypeScript 练习工具
  • 联合交叉类型
  • 类型保护与类型守卫
  • Merge 函数类型实现
  • 函数返回值类型
  • TypeScript 工程应用

为什么是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?

  • 静态类型

    • 可读性增强:基于语法解析 TSDoc,IDE 增强
    • 可维护性增强:在编译阶段暴露大部分错误
      => 多人合作的大型项目中,获得更好地稳定性和开发效率
  • JS的超集

    • 包含于兼容所有 JS 特性,支持共存
    • 支持渐进式引入和升级

image.png

image.png

基本语法

基础数据类型

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

对象类型

  • 定义一个接口
    • 只读属性:在属性名前加上readonly关键字
    • 可选属性:在属性名后加上 ?
interface IBytedancer {
  /* 只读属性:约束属性不可在对象初始化外赋值 */
  readonly jobId: number;
  name: string;
  sex: 'man' | 'woman' | 'other';
  age: number;
  /* 可选属性:定义该属性可以不存在 */
  hobby?: string;
  /* 任意属性:约束所有对象属性都必须是该属性的子类型 */
  [key: string]: any;
}
  • 定义一个对象实现接口
const bytedancer: IBytedancer = {
  jobId: 11111,
  name: 'shen',
  sex: 'woman',
  age: 21,
  hobby: 'swimming',
}

/* 报错:缺少属性"name",hobby可缺省 */
const bytedancer2: IBytedancer = {
  jobId: 2222;
  sex: 'man';
  age: 18;
}
  • 操作对象
/* 报错:无法分配到"jobId",因为它是只读属性 */
bytedancer.jobId = 12345;
/* 成功: 任意属性标注下可以添加任意属性 */
bytedancer.plateform = 'data';

函数类型

  • 声明函数
/* 给函数变量赋值一个类型声明 */
/* add函数 */
function add(x: number, y: number): number {
  return x + y;
}

/* mult函数 */
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 函数
  • typestring时,返回值为 string 类型
  • typedate 时,返回值为 Date 类型,返回当前时间
/* 对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', '2023-1-19'); // y: string
  • 定义接口 IGetDate
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" */
const getDate2: IGetDate = (type, timestamp) => {
  const date = new Date(timestamp);
  return type === 'string' ? date.toLocaleString() : date;
}

数组类型

  • 类型 + 方括号表示
type IArr1 = number[];
const arr1: IArr1 = [1, 2, 3, 4, 5];
  • 泛型表示
type IArr2 = Array<string | number | Record<string, number>>;
const arr2: IArr2 = [1, 2, '3', '4', {a: 1}];
  • 元组表示
type IArr3 = [number, number, string, string];
const arr3: IArr3 = [1, 2, '3', '4'];
  • 接口表示
interface IArr4 {
  [key: number]: any;
}
const arr4: IArr4 = ['string', () =>null, {}, []];

TypeScript 补充类型

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

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

TypeScript 泛型

  • 泛型接口 & 多泛型
interface IX<T, U> {
  key: T;
  val: U;
}
  • 泛型类
class IMan<T> {
  instance: T;
}
  • 泛型别名

type ITypeArr<T> = Array<T>;
  • 泛型约束:限制泛型必须符合字符串
  • entends 参数类型
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');
  • 栗子
function getRepeatArr(target) {
  return new Array(100).fill(target);
}

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

/* 不预先指定具体的类型,而在使用的时候再指定类型的一种特性 */
type IGetRepeatArrR = <T>(target: T) => T[];

类型别名 & 类型断言

  • 通过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;

高级类型

联合/交叉类型

  • 联合类型:IA | IB; 联合类型表示一个值可以是几种类型之一
  • 交叉类型:IA & IB; 多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

栗子-为书籍列表编写类型

  • 声明一个书籍列表
const bookList = [{
  author: 'xiaoming',
  type: 'history',
  range: '2001-2021',
}, {
  author: 'xiaoli',
  type: 'story',
  theme: 'love',
}]
  • 联合类型
interface IHistoryBook {
  author: string;
  type: string;
  range: string
}
interface IStoryBook {
  author: string;
  type: string;
  theme: string
}
type IBookList = Array<IHistoryBook | IStoryBook>;
  • 交叉类型
type IBookList = Array<{
  author: string;
} & ({az
  type: 'history';
  range: string;
} | {
  type: 'story';
  theme: string;
})>

类型保护与类型守卫

  • 定义 IA 和 IB 两个接口
interface IA {a: 1, a1: 2}
interface IB {b: 1, b1: 2}
  • 类型保护
/* 报错:类型"IA | IB"上不存在属性"a",类型"IB"上不存在属性"a" */
/* 结论:访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分 */
function log(arg: IA | IB) {
  if(arg.a) {
    console.log(arg.a1)
  } else {
    console.log(arg.b1)
  }
}
  • 类型守卫:定义一个函数,它的返回值是一个类型谓词,生效范围为子作用域
function getIsIA (arg: IA | IB): arg is IA {
  return !!(arg as IA).a;
}

function log2(arg: IA | IB) {
  if(getIsIA(arg)) {
    console.log(arg.a1)
  } else {
    console.log(arg.b1)
  }
}
  • 栗子
    • 实现函数reverse
    • 实现函数logBook了类型
    • 函数接受书本类型,并logger出相关特征
function logBook(book: IBookItem) {
  // 联合类型 + 类型保护 = 自动类型推断
  if (book.type === 'history') {
    console.log(book.range)
  } else {
    console.log(book.theme)
  }
} 

高级类型

  • 实现merge函数类型
  • 要求sourceObj必须为targetObj的子集
function merge1(sourceObj, targetObj) {
  const result = {...sourceObj};
  for(let key in targetObj) {
    const itemVal = sourceObj[key];
    itemVal && (result[key] = itemVal);
  }
  return result;
}
function merge2(sourceObj, targetObj) {
  return {...sourceObj, ...targetObj};
}
  • 类型实现繁琐:若obj类型较为复杂,则声明source和target便需要大量重复2遍
  • 容易出错:若target增加/减少key,则需要source联动去除
interface ISourceObj {
  x?: string;
  y?: string;
}

interface ITargetObj {
  x: string;
  y: string;
}
type IMerge = (sourceObj: ISourceObj, targetObj: ITargetObj) => ITargetObj;
  • 改进:泛型
  • 索引类型:关键字keyof,其相当于取值对象中的所有key组成的字符串字面量,如
    type IKeys = keyof {a: string; b: number};
  • 关键字in,其相当于取值字符串字面量中的一种可能,配合泛型P,即表示每个key
  • 关键字 ?,通过设定对象可选选项,即可自动推导出子集类型
interface IMerge {
  <T extends Record<string, any>>(sourceObj: Partial<T., targetObj: T): T;
}
type IPartial<T extends Record<string, any>> = {
  [P in keyof T]?: T[p];
}

函数返回值类型

  • 实现函数delayCall的类型声明
  • delayCall接受一个函数作为入参,其实现延迟1s运行函数
  • 其返回promise,结果为入参函数的返回结果
function delayCall(func) {
  return new Promise(resolve => {
    setTimeout(() => {
      const result = func();
      resolve(result);
    }, 1000);
  });
}
  • 关键字extends跟随泛型出现时,表示类型推断,其表达可类比三元表达式
    • 如 T === 判断类型?类型A: 类型B
  • 关键字infer出现在类型推荐中,表示定义类型变量,可以用于指代类型
    • 如 该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中
type IDelayCall = <T extends () => any>(func: T) => ReturnType<T>;
type IReturnType<T extends (...args: any) => any> = T extends (...args: any)
=> infer R ? R : any

工程应用

TypeScript 工程应用 - Web

webpack

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

相关loader

  1. awesome-typescript-loader
  2. babel-loader

TypeScript 工程应用 - Node

使用TSC编译

image.png

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