TypeScript 入门 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的的第 13 天
概述
本节课程主要分为四个方面:
-
Typescript 见解
-
Typescript 基础语法,包括类型、泛型、类型别名、类型字面量等
-
Typescript 高级类型讲解及实例
-
Typescript 工程应用介绍
为什么 什么是 TypeScript
TypeScript 发展历史
- 2012-10:微软发布了 Typescript 第一个版本 (0.8)
- 2014-10:Angular 发布了基于 Typescript 的 2.0 版本
- 2015-04:微软发布了 Visual Studio Code
- 2016-05:@types/react 发布,TypeSeript 可开发 React
- 2020-09:Vue 发布了 3.0 版本,官方支持 Typescript
- 2021-11:v4.5 版本发布
为什么 什么是 TypeScript
JavaScript:
- 动态类型
- 弱类型语言
TypeScript:
- 静态类型
- 弱类型语言
静态类型:
- 可读性增强:基于语法解析 TSDoc,IDE 增强
- 可维护性增强:在编译阶段暴露大部分错误
以上静态类型的特点在多人合作的大型项目中,获得更好的稳定性和开发效率。
JS 的超集:
- 包含于兼容所有 JS 特性,支持共存
- 支持渐进式引入与升级
编辑器推荐
- VS Code
- TypeScript 演练场
基本语法
基础数据类型
JavaScript
/* 字符串 */
const q = 'string';
/* 数字 */
const w = 1;
/* 布尔值 */
const e = true;
/* null */
const r = null;
/* undefined */
const t = undefined;
TypeScript
/* 字符串 */
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 IElementFunction = () => 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 extend 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;
高级类型
联合 / 交叉类型
为书籍列表编写类型
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>;
- 联合类型:IA | IB; 联合类型表示一个值可以是集中类型之一
- 交叉类型:IA & IB; 多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
type IBookList = Array<{
author: string;
} & ({
type: 'history';
range: string;
} | {
type: 'story';
theme: string;
})>
类型保护与类型守卫
interface IA { a: 1, a1: 2}
interface IB { b: 1, b1: 2}
function log(arg: IA | IB) {
/* 报错:类型 IA|IB 上不存在属性 a ,类型 IB 上不存在属性 a
结论:访问联合类型时,出于程序安全,仅能访问联合类型中的交集部分 */
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
// 其可将数组或字符串进行反转
function reverse(target: string | Array<any>) {
/* type类型保护 */
if (typeof target === 'string') {
return target.split('').reverse().join('');
}
/* instance类型保护 */
if (target instanceof Object) {
return target.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 intemVal = sourceObj[key];
itemVal && ( result[key] = itemVal );
}
return result;
}
function merge2(sourceObj, targetObj) {
return { ...sourceObj, ...targetObj };
}
interface ISourceObj {
x?: string;
y?: string;
}
interface ITargetObj {
x: string;
y: string
}
type IMerge = (sourceObj: ISourceObj, targetObj: ITargetObj) => ITargetObj;
/*
各类型实现繁琐:若obj类型较为复杂,则声明source和target便需要大量重复2遍
容易出错:若target增加/减少key,则需要source联动去除
/*
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];
}
// 索引类型:关键字 keyof 相当于取值对象中的所有 key 组成的字符串字面量,如
type IKeys = keyof {a: string; b: number }; // => type IKeys = "a" | "b"
// 关键字 in 相当于取值字符串字面量中的一种可能,配合泛型P,即表示每个key
// 关键字 ? 通过设定对象可选选项,即可自动推导出子集类型
函数返回值类型
//实现函数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 ? : any
//关键字 extends 跟随泛型出现时,表示类型推断,其表达可类比三元表达式
//如 T === 判断类型 ? A : B
//关键字 infer 出现在类型推荐中,表示定义类型变量,可以用于指代类型
//如 该场景下,将函数返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中
工程应用
Web
Webpack
- 配置 webpack loader 相关配置
- 配置 tsconfig.js 文件
- 运行 webpack 启动 / 打包
- loader 处理 ts 文件时,会进行编译与类型检查
相关 loader:
Node
使用 TSC 编译
- 安装 Node 与 npm
- 使用 npm 安装 tsc
- 配置 tsconfig.js 文件
- 使用 tsc 运行编译得到 js 文件
课后
-
学习官网更详细说明:www.typescriptlang.org/docs/
-
复习相关特性:www.tslang.cn/docs/home.h…
-
尝试改造项目进行实践: Todo List