typescript基础笔记

typescript早在2013年就发布了第一个正式版本,印象中一直到了19年才大火起来。
三年过去了,一直是可用可不用的状态,于是很多人都没学习使用。
直到react和vue开始捆版上了ts,前端圈也开始了“内卷”,ts已经是不得不用的状态了。

这次分享的是自己学习过程觉得掌握了就可以上手的内容,上手了之后通过项目多实践,
实践过程再学习深入的内容,应该就能比较快的掌握。

学习过程贴的代码都是在在线的这个平台的演练场调试的:
https://www.typescriptlang.org/zh/

tips:
  1. ts最终都会编译成js,添加的类型最终都会被删除,只是为了开发的时候提示
  2. ts一切类型校验的目标都是为了安全
  3. ts冒号(:)后面的都是类型
  4. ts中小写的(string)叫类型,描述基础类型,大写的(String)是类,描述的是实例
一、基础类型
boolean、number、string
let num: number = 3;
let have: boolean = true;
let str: string = 'str';
null、undefined

null和undefined对应的类型就是本身。不开启严格模式的时候可以赋值给任何类型,
也就是任何类型的子类型。一般会开启,所以null和undefined只能是自己的类型。

strictNullChecks:严格检测null undefined

let nu: null = null;
let un: undefined = undefined;
any

ts中所有的类型都可以是any类型,使用any类型也意味着放弃类型校验。
除非特殊情况,并不建议使用。

unknown

为了解决any带来的问题所出现的类型,也是所有类型都可以是unknown类型。
unknown类型被当作安全的类型。

unknown只能赋值给unknown或者any:

let a: unknown = 1;
let b: any = a;
let c: number = a;//err

unknown类型不能进行运算、调用属性、当作函数:

let a: unknown;
a.toString();//err
a();//err
let b = a + 1;//err

unknow使用的时候要把类型具体化,缩小使用范围:

function isString(val: unknown): void{
    if(typeof val === 'string'){
        val.toString();
        val.toFixed();//err
    }
}

unknown会被当作安全类型的原因是不能进行运算、调用属性、当作函数,
使用的时候类型要具体化,缩小使用范围,这样就可以避免any的那些不安全的副作用。

void

void类型只是当作函数的没有返回值的时候使用,表示没有任何类型。函数不加void类型,
默认返回void,所以如果函数没有返回值的时候void加不加感觉都可以。

Array

ts的数组有两种写法,常规写法:

let arr1: string[] = [''];
let arr2: (string | number)[] = ['', 3];

另外一种泛型写法,会比较少用,把[]变成Array,类型写到尖括号里面:

let arr1: Array<string> = [''];
let arr2: Array<string | number> = [''];

一般开放会用到数组对象([{}]),声明一般两种方法:

interface ListItem{
    name: string
    id: number
    label: string
}
let GroupList1:ListItem[] = [{
    name: '',
    id: 1,
    label: ''
}]
let GroupList2:Array<ListItem> = [{
    name: '',
    id: 2,
    label: ''
}]
Tuple

元组,ts衍生的一种类型,限制数组的长度和顺序:

let tuple:[string, number, boolean] = ['str', 3, true];

元组已知数组的长度和每个子元素的类型,不能多也不能少。
可以push已知类型的子元素,但是无法使用。

Enum

ts的枚举只是为了清晰地表达意图或创建一组有区别的用例。

数字枚举:

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}
console.log(Direction.Up);//0
console.log(Direction[0]);//Up

数字枚举会自动增长,如果第一个不初始化赋值会从0开始。
枚举可以通过名字和下标访问,枚举值和枚举名字互相映射。
编译后的代码:

"use strict";
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

字符串枚举:

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

字符串枚举获取值的方式不能使用下标。

异构枚举:
数字和字符串混合使用,几乎不会使用,因为没什么意义:

enum Direction {
    Up = "Up",
    Down = "DOWN",
    Left = 1,
    Right,
}
object

object表示的是非原始数据类型,
也就是除number,string,boolean,symbol,null或undefined之外的类型。

let a: object = [3, 'str'];
let b: object = {obj: 3};
let c: object = function(){}
never

never意味着永远达不到,函数返回值使用,报错、死循环可以做到,
通常用来校验代码完整性,实现类型的绝对安全,一般很少做这么严格的校验。

二、断言

ts的断言其实就是手动断定是什么类型,主要是为了欺骗编辑器,只在编译阶段起作用,
编译之后断言就移除了,所以使用的时候一定要注意自己断言的结果。

非空断言!

断定某个变量不是空的,也就是不会是undefined或者null:

let a: number;
a+=1;//err

let b: number;
b!+=1;

function setName(name: string | undefined){
    let myName1: string = name;//err
    let myName2: string = name!;
}
as语法

把某个类型断定成某个类型:

function getVal(obj: string | number){
    (obj as number).toFixed();
}

上面的obj可能是string可能是number,string没有toFixed,这时候用as语法,
表示确定obj一定是number。

三、类型保护(类型守卫)

类型保护是运行时的一种检测,当一个变量不确定是什么类型的时候,可以调用共有的属性和方法,
特有的属性和方法就需要配合类型保护。

js提供typeof 、instanceof、 in
//typeof
function getVal(obj: string | number){
    if(typeof obj === 'number'){
        obj.toFixed();
    }
}
//in
type Obj1 = {
    a: number
}
type Obj2 = {
    b: number
}
function getTh(obj: Obj1 | Obj2){
    if('a' in obj){
        console.log(obj.a)
    }
}
//instanceof
type Fn = () => {}
function run(fn: Fn | number){
    if(fn instanceof Function){
        fn();
    }
}
自定义

除了js提供的,ts也可以自定义类型保护,使用is关键字,
is关键字一般用于函数返回值类型中,判断参数是否属于某一类型,并根据结果返回对应的布尔类型:

function isObject(val: any): val is Object{
    return Object.prototype.toString.call(val) === '[Object object]'
}

type Dog = {
    type: 'dog'
    eat: () => {}
}
type Cat = {
    type: 'cat'
    speak: () => {}
}
//如果没有animal is Dog,下面调用animal.eat()是会报错,ts中,直接返回true或者false并不能判断是Dog还是Cat
function getIs(animal: Dog | Cat): animal is Dog{
    return animal.type === 'dog';
}
function isDog(animal: Dog | Cat){
    if(getIs(animal)){
        animal.eat();
    }
}

四、联合类型和交叉类型

联合类型其实就是用|组合,交叉类型用&:

//联合类型
let a: string | number;

type A = string | number;
type B = string | boolean;
//交叉类型
let b: A & B = 'str';

交叉类型如果是基本数据类型,合并的时候同名会出现never:

type Obj1 = {
    a: string
    b: number
}
type Obj2 = {
    a: number
    c: string
}
type Obj = Obj1 & Obj2;

Obj相当于:

type Obj = {
    a: never;
    b: number;
    c: string;
}

出现never其实就是报错,一般不会出现。

五、接口interface和类型别名type

接口和类型别名在ts中非常重要,这两个在一般情况可以通用,也有区别。
一定要记住,声明interface和type的时候用类型,实现的时候用具体的值。

接口interface

接口用来描述数据的形状,没有具体的实现(抽象的),接口可以描述函数、对象、类。
(语法上分号和逗号可写可不写)

描述对象:

interface Person{
    age: number
    name: string
}

可选、只读属性:

interface Person{
    readonly age: number
    name?: string
}

一般接口定义的属性一定要实现,修饰符?表示可选,函数参数也是这样使用。
只读是实现的时候初始话可以赋值,之后赋值就会报错。

任意属性:

interface Person{
    age: number
    name: string
    [other: string]: any
}

语法就是key用[]包裹,key用什么类型,值一般用any,否则已经定义的和这边的都要统一一个类型。

扩展属性:

接口定义好之后,想要扩展属性,一般使用继承或者自动合并:

interface Person{
    age: number
    name: string
}
//自动合并
interface Person{
    sex: string
}
//继承
interface Son extends Person{
    sex: string
}

其它方法用as断言、使用ts的兼容性几乎不用。

类型别名type

类型别名也可以用来描述对象和函数,还可以描述原始类型、元组、联合类型等。
语法上面类型别名是=。

type Person = {name: string, age: number};
type Name = string;
type Arr = [string, number];
type Uni = Person | Arr;
接口和类型别名的区别

接口和类型别名很多时候会分不清,因为用起来一个样,把区别理清楚了就知道两者的通用和不同。

函数语法区别较大:

type Fn1 = (a: string) => number
interface Fn2{
    (a:string): number
}
let fn: Fn2 = (a: string) => {
    return 5
}

接口还是对象形式,参数:返回,类型别名则直接是箭头函数。

type可以使用联合类型和具体的值和元组:

interface Obj{
    a: string
}
type Tu = [string];
type A = 1;
type Uni1 = Tu | A | Obj;
type Uni2 = Tu & A & Obj;

type的联合类型可以联合接口,使用联合类型就相当于扩展type,没办法扩展自身。

接口可以继承和多继承,接口还可以继承类型别名,会自动合并,可扩展:

type Obj1 = {a: string}
interface Obj2{
    b: string
}
interface Obj2{
    c: string
}
interface Obj3 extends Obj1, Obj2{
    d: string
}

type可以内部使用in语法,interface无法使用in语法:

type Obj1 = {a: string, b: number}
type Obj2<T> = {[K in keyof T]: T[K]}

这个在后面泛型使用的时候很有用。

类可以实现接口或类型别名,但类不能实现联合类型的别名:

interface Obj1{
    a: string
}
type Obj2 = {b: string};
type Obj3 = Obj1 | Obj2;
class O1 implements Obj1{
    a = 'a'
}
class O2 implements Obj2{
    b = 'b'
}
//err
class O3 implements Obj3{
    b = 'b'
}

六、泛型

泛型在ts中非常重要,使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。
这样用户就可以以自己的数据类型来使用组件。
在定义类型的时候还不能确定是什么类型,在使用的时候才能确定类型。根据传入的类型决定类型。

语法:
function fn<T, K>(a: T, b: K): K{
    return b
}

尖括号里面相当于参数,可以是任意的名字,一般使用:

  • T:type
  • K:key
  • V:value
  • E:element
    泛型是类型,并不是具体的参数。

调用的时候如果不具体类型,会根据参数推论出:

fn(1, '');
//不常用
fn<number, string>(1, '');
接口、类型别名泛型:
interface Eat<T>{
    (a: T): T
}
let eat: Eat<string> = () => 'a';
eat('');

interface Food<T>{
    name: T
}
let per: Eat<Food<string>> = (a) => a;
per({name: 'w'})

type Speak<T> = (a: T) => T;
let speak: Speak<string> = (a) => a;
speak('');
泛型约束extends

用来约束泛型的范围,约束要满足约束的特点,满足是拥有特性,只要含有要求的属性

interface Ex{
    name: string
}
function fn<T extends Ex>(obj: T){

}
fn({name: '', age: 3})

这边是只要有name属性就可以,如果我返回值设置T,是不行的:

interface Ex{
    name: string
}
function fn<T extends Ex>(obj: T): T{
    return T //err
}
fn({name: '', age: 3})

T只是约束了具有name属性,但是不能保证T一定是原来的T,可以增删属性。

typeof、keyof、in

typeof:可以用来获取一个变量声明或对象的类型

interface Person{
    name: string
    age: number
}
let person: Person = {name: '', age: 3}
//等价
type PersonS = typeof person;
type PersonO = Person;

keyof:获取某种类型的所有键,其返回类型是联合类型

interface Obj{
    name: string
    age: number
}
type Obj1 = keyof Obj;//"name" | "age"
let obj1: Obj1 = 'name';
let obj2: Obj1 = 'age';

in:用来遍历,看结果是遍历联合类型

type keys = 'name' | 'age';
type Obj1 = {
    [K in keys]: any
};
let obj1: Obj1 = {name: '', age: 3};
条件类型分发

泛型中如果通过条件判断返回不同的类型,放入的是联合类型(|),具备分发功能:

type Obj1 = {name: string};
type Obj2 = {age: number};
type JudgeObj1<T extends Obj1 | Obj2> = T extends Obj1 ? Obj1 : Obj2;
type IsObj1 = JudgeObj1<Obj1>//{name: string}

分发的理解就是,T会先跟Obj1判断,再和Obj2判断,而不是Obj1 | Obj2看成一个整体。

通过内置类型Exclude可以更好的理解(删除第二个参数存在的类型):

//type Exclude<T, U> = T extends U ? never : T
type Ex = Exclude<number | string | boolean, number>
  • number extends string ? never : number得到number
  • string extends string ? never : number得到never
  • boolean extends string ? never : number得到boolean
  • 所以最终结果是string | boolean。
    会分别把没一个类型去校验。
内置类型、infer

内置类型和infer可以后面好好了解,这边列举几个常用的内置类型

  • Readonly:只读
  • Exclude:删除第二个参数存在的类型
  • Extract:返回符合第二个参数的类型
  • Required:全部变成必填
  • Partial:让所有属性都变成可选
  • NonNullable:去除null和undefined
  • Pick:在对象中挑选
  • Omit:忽略对象中的

.d.ts

ts会检测根目录下所有.d.ts文件,里面用declare声明的都是全局的。
declare声明的都没有具体的实现,.d.ts只是为了使代码不报错,没有任何实际功能。

比如用script引入jq,直接$()会报错,在.d.ts声明:

declare function $(){}

ts还有很多需要学习,这些只是简单的了解,然后在开发过程中再慢慢学习其它内容。

欢迎关注订阅号 coding个人笔记

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值