TypeScript 的发展与基本语法

目录

一、为什么什么是TypeScript?

1、发展历史

2、typescript与javascript

3、静态类型的好处

二、基础语法

1、基础数据类型

2、补充类型

3、泛型

4、泛型的高级语法

5、类型别名&类型断言

扫描二维码关注公众号,回复: 16504400 查看本文章

6、字符串/数字 字面量

三、高级类型

1、联合/交叉类型

2、类型保护与类型守卫

3、merge函数

4、函数返回值类型

四、工程应用

1、浏览器Web--webpack

2、NodeJs

五、学习总结

实例:当函数及传入的参数可能为任意类型时


一、为什么什么是TypeScript?

1、发展历史

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版本发布

2、typescript与javascript

(1)typescript:静态,弱类型的语言

(2)javascript:动态,弱类型的语言

(3)动态、静态类型
①动态类型:是在执行阶段才确定类型的匹配
javascript是脚本语言,代码可以直接贴到浏览器的控制台执行,执行过程中,浏览器js解析引擎才会进行类型的匹配,当输入类型错误时,报错即动态类型的一个特征,是在运行是才进行类型的检验/匹配
②静态类型:会提前进行类型的检验/匹配

(4)强、弱类型
①弱类型,当字符串'1'和数字1相加时,js在执行时会进行隐式类型转换
②强类型,字符串'1'和数字1不能相加

3、静态类型的好处

(1)可读性增强:基于ts的语法解析TSDoc,ide可以得到增强
当写完一段代码(函数/对象),过一段时间就容易遗忘,难以整理成文档时
①TSDoc语法解析,可以自动生成文档,同时类型也会被说明含义,也就是在写类型时,相当于在写文档,节约解读代码所花费的时间
②ide:有很强的类型提示

(2)可维护性增强:在编译阶段暴露大部分错误
编译暴露的问题:语法拼写错误,类型匹配错误(当把字符串传给函数,但是函数需要的是对象)等等 基于上面两点,使用typescript在多人合作的大型项目中,能获得更好的稳定性和开发效率

(3)ts是js的超集
①包含于兼容所有js特性,支持共存
②支持渐进式引入与升级

二、基础语法

1、基础数据类型

(1)常见的基础数据

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

(2)对象类型
自定义对象类型

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,
}

(3)函数类型

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;

(4)函数重载

/*对getDate函数进行重载,timestamp为可缺省参数*/
function getDate(type: 'string ', timestamp?: string): 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"*/
const getDate2: IGetDate = (type, timestamp) => {
    const date = new Date(timestamp);
    return type === 'string' ? date.toLocaleString() : date;
}

(5)数组类型

/*「类型+方括号」表示*/
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, {}, []];

2、补充类型

/*空类型,表示无赋值*/
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>;

3、泛型

(1)场景1
①设置一个数组,可以传入任意类型,希望传入数字时输出数字的Array,传入字符串时输出字符串的Array,不然会组成数据丢失的情况,因为会被重写成any,达不到效果
②那么就需要泛型,可以不预先指定数据的类型,在数据使用的时候,根据内容才确定其类型

function getRepeatArr(target) {
    return new Array(100).fill(target);
}
type IGetRepeatArr = (target: any) => any[];
/*不预先指定具体的类型,而在使用的时候再指定类型的一种特性*/
type IGetRepeatArrR = <T>(target: T) => T[];

(2)其它场景
泛型其实就是一种变量指代

/*泛型接口&多泛型*/
interface IX<T, U> {
    key: T;
    val: U;
}
/*泛型类*/
class IMan<T>{
    instance: T;
}
/*泛型别名*/
type ITypeArr<T> = Array<T>;

(3)注意定义区别
①函数:在函数定义的()前,使用<>,<>中的内容就是泛型
②类/别名/接口:都是在该类型别名后,使用<>,<>中的内容就是泛型

4、泛型的高级语法

(1)泛型约束
泛型需要限制在一定范围内(如:它可以是传入的时候被使用但是必须有一个限定)
(2)泛型参数默认类型

/*泛型约束:限制泛型必须符合字符串*/
// extends表示类型约束
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');

两种区别:上面是函数泛型,下面是类型别名的泛型

5、类型别名&类型断言

/*通过type关键字定义了I0bjArr的别名类型*/
// 用一个变量承载比较复杂的类型
type IObjArr = Array<{
    key: string;
    [objKey: string]: any;
}>
// keyBy函数将arr转换成object
// reduce()函数给result传入一个空obj(因为result的类型没有被指定,ts会进行默认类型推断)
function keyBy<T extends IObjArr>(objArr: Array<T>) {
    /*未指定类型时, result类型为f}*/
    const result = objArr.reduce((res, val, key) => {
        res[key] = val;
        return res;
    }, {});
    /* 通过as关键字,断言result类型为正确类型*/
    return result as Record<string, T>;
}

6、字符串/数字 字面量

/*允许指定字符串/数字必须的固定值*/
/* IDomTag必须为html、body、div、span中的其一*/
type IDomTag = 'html' | 'body' | 'div' | 'span' ;
/* IOddNumber必须为1、3、5、7、9中的其一*/
type IOddNumber = 1 | 3 | 5 | 7 | 9;

三、高级类型

1、联合/交叉类型

(1)原始写法

// 为书籍列表编写类型
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>;

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

type IBookList = Array<{
    author: string;
} & ({
    type: 'history';
    range: string;
} | {
    type: 'story';
    theme: string;
})>

2、类型保护与类型守卫

(1)原始写法

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);

    }
}

(2)类型守卫
①额外定义了getIsIA()函数,可以接收一个参数【IA或IB】
②判断时写新语法,类型谓词is,判断是否是IA
③返回时使用断言as,当存在a时,断言一定是IA

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);
    }
}

(3)typeof、instance类型保护
reverse()只有数组才支持,字符串不支持,要先把字符串转为数组

//实现函数reverse
//其可将数组或字符串进行反转
function reverse(target: string | Array<any>) {
    /* typeof类型保护*/
    if (typeof target === 'string') {
        return target.split("").reverse().join("");
    }
    /* instance类型保护*/
    if (target instanceof Object) {
        return target.reverse();
    }
}

(4)总结
①当两个类型完全没有任何交集时,才需要写类型守卫
②回到前面书籍数据的例子

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

3、merge函数

(1)传统类型定义

/**
*merge函数类型,实现合并sourceObj、targetObj
*要求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联动去除
*/

(2)使用泛型
①IMerge对象

  • extends类型约束,Record高级类型
  • Record<string, any>泛型分别表示指定object中key和value的类型,即object的any类型的简单表达
  • 对T泛型做限定必须是object类型,targetObj就是一个T,返回的也是T
  • IPartial通过传入任意类型后,得到其子集

注意:函数的泛型定义是在()前面定义的,类型泛型定义是在类型后面定义的

②IPartial

  • keyof:相当于取值对象中的所有key组成的字符串字面量, 如type IKeys = keyof { a: string; b: number }; // => type lKeys = "a" | "b"
  • in:相当于取值字符串字面量中的一种可能,配合泛型P, 即表示每个key
  • ?:通过设定对象可选选项,即可自动推导出子集类型
  • IPartial通过泛型,要求传入的必须是对象,然后通过keyof取出所有的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];
}

4、函数返回值类型

(1)对函数类型的定义,就是对函数的入参出参定义
下面的delayCall函数例子中,入参是一个函数,而出参是一个Promise函数返回值

//实现函数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表示,使用在类型推荐命中的结果中

四、工程应用

1、浏览器Web--webpack

(1)配置webapack loader相关配置
webapack loader:将webpack中不能识别的文件,转换成webpack可以识别的文件
(2)配置tsconfig.js文件
(3)运行webpack启动/打包
(4)loader处理ts文件时,会进行编译与类型检查

2、NodeJs

(1)使用TSC进行编译:将ts文件转换成js文件
(2)过程
①安装Node与npm
②配置tsconfig.js立件
③使用npm安装tsc
④使用tsc运行编译得到js文件

五、学习总结

通过本次学习,对ts的泛型有了更清晰明确的认识

实例:当函数及传入的参数可能为任意类型时

(1)普通写法
给函数及传入的参数定义any类型

function identity(msg:any):any{
    return msg;
}

由于算法的复用,会导致这个函数可以接收任何类型的msg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的

(2)泛型写法
给函数添加泛型类型变量T,T能够捕获用户传入的类型,然后使用这个类型
实现在函数执行时才去获取类型

function identity<T>(msg:T):T{
    return msg;
}

“精辟总结”:普通函数要求我们对编程,泛型则要求我们对类型编程

猜你喜欢

转载自blog.csdn.net/qq_51478745/article/details/132466363