前言
这是我参与 [第五届青训营] 伴学笔记创作活动的第 4 天,上回讲到Javascript,那这个Typescript是什么呢? 为什么要用Typescript呢?请听我娓娓道来!!!
为什么要用 TypeScript ?
JS 是动态类型 弱类型语言 TS 是静态类型 强类型语言
TypeScript 基础和语法
- 静态类型
- 可读性增强:基于语法解析TSDoc,ide增强
- 可维护性增强: 在编译阶段暴露大部分错误 => 多人合作的大型项目中,获得更好的稳定性和开发效率
- JS 的超集
- 包含于兼容所有Js特性,支持共存
- 支持渐进式引入与升级
对象类型
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,
}
总结: 对象类型
interface I名字{ xx: 类型, [key:类型]: 类型 }
函数类型
function add(x,y) {
return x + y;
}
const mult = (x,y) => x * y;
function add(x:number,y:number) {
return x + y;
}
const mult: (x: number,y: number) => number = (x,y) => x * y
interface IMult {
(x:number, y:mumber): number';
}
const mult:IMult = (x,y) => x * y
总结: 函数类型 函数就是一个入参和出参的类型判断
interface I名字 { (x:类型):类型 }
函数重载
/* 对getDate函数进行重载,itimestamp为可缺省参数 */
function getDate(type: 'string', timestamp?: string): string;
interface IGetDate {
(type:'string', timestamp?: string): string;
(type: (type: 'date', timestamp?: string): Date;
(type: 'string''date', timestamp?: string): Date | string;
}
/* 不能将类型“(type: any,timestamp: any) => string Date”分配给类型“IGetDate”
不能将类型“stringDate”分配给类型“string”。
不能将类型“Date”分配给类型“string”。ts(2322) */
const getDate2: IGetDate = (type,timestamp) => {
const date = new Date(timestamp);
return type === 'string' ? date.toLocaleString() : date;
}
总结: 函数重载
interface I名字 {
(xx:类型,xx:类型):类型;
(xx:类型|类型,xx:类型):类型|类型;
}
数组类型
/* r类型 + 方括号] 表示 */
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,{},[]];
总结: 数组类型
- 全是数字的数组 (推荐)
type I名字= number[]
- 泛型 (推荐)
type I名字 = Array<类型 | 类型| Record<类型,类型>>
- 接口表示
interface I名字 {
[xx: 类型]: any类型;
}
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补充类型
暂时理解不了
理解不了的原因是: 1. 英文不认识 2. 文档没查过 3. 代码没见过
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).filltarget);
/* 报错: 类型“number”的参数不能赋给类型“string”的参数 */
getStrArr(123);
/* 泛型参数默认类型 */
type IGetRepeatArr<T = number> = (target: T) => T[];
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);
/* 报错: 类型“string”的参数不能赋给类型“number”的参数 */
getRepeatArr('123')
总结: Typescript泛型 需要看= 和 => 去到底是给哪个参数进行类型约束,还是给默认值
类型别名 & 类型断言
/* 通过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;
Typescript 高级类型
case | solution | evolution
联合/交叉类型
为书籍列表编写类型 =>
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) {
/* 报错: 类型“IAlIB”上不存在属性“a”。类型“IB”上不存在属性“a”。*/
/* 结论: 访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分 */
if (arg.a) {
console.log(arg.a1)
} else {
console.log(arg.b1);
}
}
下面看下如何进行改良
interface IA { a: 1,a1: 2 }
interface IB { b: 1,b1: 2 }
/* 类型守卫: 定义一个函数,它的返回值是一个类型谓词生效范围为子作用域 */
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);
}
}
上面是通过 is 来去确定是 IA ,通过as IA 来去让Typescript 识别就是IA 总体比较麻烦
以下就是Typescript 智能的推断
// 实现函数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 }
};
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: IPartial<T>, targetObj: T): T;
}
type IPartial<T extends Record<string, any> > = {
[P in keyof T]?: T[P];
}
// 索引类型: 关键字(keyof],其相当于取值对象中的所有key组成的字符串字面量,如
type lKeys = keyof { a: string; b: number }; // => type lKeys = "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) => IReturnType<T>;
type IReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
// 关键字(extends] 跟随泛型出现时表示类型推断,其表达可类比三元表达式
// 如 T=== 判断类型 ? 类型A : 类型B
// 关键字[infer] 出现在类型推荐中,表示定义类型变量,可以用于指代类型
// 如 该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中
总结: 函数返回值类型
- 先找分解符=
- 判断等式的两边,那边是声明,那边是推断类型
- 找<> 进行判断层数里的类型声明或者推断
- 找到关键字和符号 列如:extends infer X ? : any
- 再进行整体类型推断
Typescript工程应用 -Web
webpack
- 配置webapack loader相关配置
- 配置tsconfg.js文件
- 运行webpack启动 / 打包
- loader处理ts文件时,会进行编译与类型检查
babel 相关loader:
- awesome-typescript-loader 2.babel-loader
Typescript工程应用 -Node
-
使用TSC 编译 code.ts -> tsc[code.ts] -> code.js
-
安装Node与npm
-
配置tsconfig.js文件
-
使用tsc 运行编译得到js文件