TypeScript—从入门到入门

前言:

TypeScript简单来说是一门JavaScript的升级版本,它的好处是可以在写代码的时候给你一些使用的提示,并且能够防止低级错误的出现在你写的代码中等等!

TypeScript的使用从一个空的文件夹开始,并且浏览器并不认识ts文件,需要将其转换为js文件给浏览器运行(把tsconfig.json文件中的OutDir属性打开并设置为./dist)

TypeScript的使用:

安装webpack和TypeScript的基本模块:

// 初始化一个package.json文件管理安装的各种包
npm init -y

// 安装webpack以及打包需要使用到的webpack-cli和webpack-dev-server
npm i webpack@4.41.5 webpack-cli@3.3.10 webpack-dev-server -D

// 安装TypeScript以及打包过程中需要使用的插件
npm i typescript ts-node @types/node @types/webpack --save-dev

配置webpack.config.ts文件:

要使用Typescript来编写 webpack 配置,需要先安装必要的依赖,

import * as path from 'path';  
import * as webpack from 'webpack';

// 引入 'webpack-dev-server' 防止 TypeScript 报错  
import 'webpack-dev-server';

const config: webpack.Configuration = {  
    mode: 'production', // 模式为生产环境  
    entry: './foo.js', // 入口文件路径  
    output: {  
        path: path.resolve(__dirname, 'dist'), // 输出目录  
        filename: 'foo.bundle.js', // 输出文件名称  
    },  
    devServer:{
        port:"3000"//端口号
        proxy: {}, // 代理
    },
    ... // 以及更多的配置
};

export default config

父类型与子类型:

在TypeScript中,父类型会包含子类型的所有成员,也就是说如果A是B的父类型,那么A会兼容B,会导致满足B类型的数据可以直接赋值给A变量,其次,类型会决定取值的范围,如果类型的兼容性同时满足的时候,那就可以赋值了,最后,在TypeScript中使用=,就会存在兼容性的判断,如果没有通过兼容性的判断就会报错

const a: A = c // 这里会出现C这个值是否是类型A的子集
const b: B = D // 这里会出现D这个值是否是类型B的子集

// 常量b可以赋值给a => a是b的父类型
a = b

// 常量a可以赋值给b => b是a的父类型,然后a是b的父类型 <=> a、b的类型相同
b = a

TypeScript的类型:

基础类型:

// 字符串
let a: string = "字符串"// 字符串范围类型收窄:
let a1: "男" = "男"// a1能够取到的值只有男
let a2: "男" | "女" = "男"// a2能够取到的值只有男和女两种

对于类型而言,父类型中的每一个子类型都可以称为一种类型,它们可以单独形成类型也可以组合形成类型

// 数字:
let b: number = 13// 数字同样适用:
let a1: 1 = 1// a1能够取到的值只有1
let a2: 1 | 2 = "男"// a2能够取到的值只有1和2两种
// undefined(值只有undefined一个):
let d: undefined = undefined
// 布尔值(值只有false和true):
// 这里按照上面的规定就会发现c和c1是等价的
let c: boolean = false;
let c1: true | false = true
// null(值只有null一个):
let e: null = null
// 数组类型:它是一个复合结构,因为它里面元素的类型是不固定的

// []:空数组类型
let f: [] = [] // f表示一个空数组



// 纯**类型的数组
let f1: number[] = [1,2,3,4] // 纯数字数组
let f1: string[] = ['1','2','3'] // 纯字符串数组
let f1: boolean[] = [true] // 纯布尔值数组
... // 等等



// 纯**类型的数组的改写形式
let f11: Array<number> = [1,2,3,4] // 这里f1和f11是等价的’
... // 等等



// 纯空数组
let f2: [][] = [[],[],[]] // 纯空数组



// 二维数组内部的元素是**类型的数组
let f3: number[][] = [[1],[1],[1]] // 二维数组内部的元素是数字类型的数组
... // 等等

// 元组:元组可以定义多种数据类型,但是元组的缺点在于数组的长度会被固定

let g: [number, string] = [1, '2'] // g的长度和元素的类型都会被限制
// 函数类型:使用的时候需要定义参数类型和返回值类型
// 对于TypeScript中的函数来说,他支持ES6中所有的函数的写法...

// 普通函数的写法:
function echo(msg: string...): string {
    return msg
} // 这里echo函数的参数类型为字符串,返回值类型为字符串

const echo3 = (msg?: number): number => msg // 可选的参数

const echo4 = (msg: number = 5): number => msg // 参数默认值

const echo6 = (...arg: number[]): number => msg // ...扩展运算符



// 箭头函数的写法:
const echo1 = (msg: string): string => msg
const echo2: (msg: string) => string = (msg) => msg // 把函数的逻辑当做一个变量来对待 

函数的重载:同一个函数,在参数类型、数量不一样的时候会有不同的调用结果(简单来说就是将函数执行存在的情况写出来,但是函数的主体逻辑只写一次),它主要解决某个具体调用的场景下函数对应的类型

// 举例:简单实现一下jQuery中的attr的方法
// 传入两个参数获取节点的属性名,传入三个参数获取节点的属性名和属性值
function attr(dom: HTMLElement, attrName: string): string
function attr(dom: HTMLElement, attrName: string, attrValue: string): void
function attr(dom: HTMLElement, attrName: string, attrValue?: string): string | void {
    if(attrValue) {
        dom.setAttribute(attrName, attrValue)
    } else {
        return dom.getAttribute(attrName)
    }
}

函数的兼容性(参数和返回值):如果两个函数的参数相同的情况下,函数的兼容性等价于函数返回值类型的兼容性,如果两个函数的返回值的类型相同的情况下,函数的兼容性等价于参数对象兼容性的取反的情况

特殊类型:

顶部类型(是所有类型的父类型):anyunknow
底部类型(是所有类型的子类型):undefinednull(非严格模式,never除外)never(主要以函数返回值的形式)any

// void类型:当一个函数没有返回值时,函数的返回值就是void
function echo(msg: string): void {}
// never类型:表示不存在的类型(用于函数死循环、没有返回值、报错等情况表示函数的返回值)

function loop(): never {
    while (1) {
        console.log(111)
    }
} // 没有返回值的情况

function error() {
    throw new Error("出现错误")
} // 出现报错的情况
// any类型:如果一个变量定义成any类型,那么TypeScript对其放弃类型检查,跟JS一样

let l: any = 123"123" 、[] 、{} ... // 所有类型都可以给any赋值

如果ts文件中的全部类型都写成any类型,那么这个ts文件跟js文件实质上并没有任何差异,所以在不是必要的情况下,不要轻易写any

// unknown类型:表示值得类型不可知或者未定义跟any类型,都是顶部类型,只不过
//             TypeScript没有对其放弃类型检查,使用起来是安全的,但是unknown
//             类型在没有被转变为确定类型之前是不能赋值给除它和any以外的类型的

let m: unknown; // 此时变量m的类型是unknown,在没确定其类型之前,是不能操作的
// 对象类型:

// 一、对象类型的一般描述:
let n: {} = {} <=> let n = {} // {}表示空对象类型,是一个没有成员的对象,当你
                              // 想要取其属性来赋值取值就会报错,但是可以使用
                              // 定义在该对象上的所有属性和方法
                              
let o: object = [1,2,3]; // object表示除简单数据类型以外的类型(一般创建引用类型)。
let o1: object = new Data() // 可以使用new关键字来创建

let o: object = [1,2,3]; // 大写的Object定义的类型跟JS中的对象本质上没有什么区别



// 二、使用接口表示对象的类型: 跟js一样,属性等价于变量,方法等价于函数
interface Iabc { // 接口在定义的时候一般在其前面加上一个I,表示这是定义的一个接口
    name?: string // 在属性名后面加上一个?来表示这是一个可选的属性
    readonly age: number // 在属性名前面添加一个readonly关键字表示这个属性是一个只读的属性
    [key: string]: any // 表示我可以添加除接口中定义好的属性以外的属性(对象的key是字符串,值是任意类型)
    sound: (msg: string) => void
} // 这里的接口就是自定义的类型,其次定义的属性名与方法名需要与接口一一对应且名字相同

const p1: abc = {// 使用接口abc对对象p1进行类型约束
    name: "zhangsasn",
    age: 18
    sound(msg: string): void {
        console.log(msg)
    } // 函数的定义最好都给其参数类型和返回值类型
}

p1.sound() // 使用p1的方法



// 三、使用type的方式表达对象类型
interface Iabcd { } // 长相上就是使不使用等号的区别



// 四、使用接口定义类数组类型:数组可以理解为一种以数字类型为键的特殊对象(不常用)
// 类数组结构特征:其每一个属性的类型都是一个数字,并且具备length的属性
interface Iarray = {
    [key: number]: any
    length: number // 定义一个类数组结构这个是必须的
} // 定义一个任意类型的类数组



// 五、使用接口定义函数类型
// 当接口中只有一个成员,该成员是一个方法,并且该方法没有名字,那就可以描述函数
interface Ifn {
    (params: number): number
} // 定义对象和函数的主要区别在于方法有没有名字并且方法的数量只有一个

const fn4: Ifn = (params: number) => 12

{}、object、Object类型都不是null和undefined类型的父类型(这个地方版本不一样可能会有差异,如果发现请留言,我会及时更改)

在对象类型中,如果处于兼容的情况下,子类型必须包含父类型的所有类型(也就是父类型的属性肯定比子类型少,那么空对象肯定是所有对象的父类型)

TypeScript的类型转化

类型别名(type): 就是给类型起一个额外的名称或者一个新的类型名称

type str = string // 给string这个类型起另一个的名字叫str,这个str的类型也是string

type sn = string | number // 给定义的一个新的类型起一个名字,此时这个sn表示可以string类型或者是number类型

联合类型(|): 联合类型是将子类型结合起来生成一个父类型,这个类型是所有结合的子类型的父类型。

string | number // 这个联合类型表示如果取值的话,可以取string和number两种类型,跟数学中的(||)类似,就是可以选择吧。

交叉类型(&): 交叉类型类型就是将子类型进行结合产生一种新的类型,有一点取交集的意味,如果没有交集的话就会出现never类型

type sn = number & 3 // 这里交叉的话会sn表示的是类型3

type sn = string & number // number和string类型并没有什么交集产生,将其结合的话会没有这个类型也就死never类型

type sn = { name: string } & { age: number }
type sn = { name: string, age: number } // 上面的类型交叉的话等效产生这个对象类型

type sn = { name: string } & { name: number, age: number }
type sn = { name: never, age: number } // 如果属性名出现交叉的情况那么会先将属性名进行交叉,再将类型进行交叉

数组产生联合类型

type t = [number, string, boolean]
let arr: t = [1, "2", true] // 这里定义了一个元组

// 如果需要将类型t中的所有类型取出变成一个联合类型:
type t1 = t[0] // t是定义的一个类型数组,如果取0的话,那么就是会取到第一个元素,也就是number类型,此时t1表示number类型
type t1 = t[1] // string类型
... // 等等
type t1 = t[number] // 那么这里的0、1、这些数字就可以使用number类型进行表示,因为number类型是这些数字类型的父类型,他包括了这些子类型,所以他可以取到整个数组里面所有的值

类型断言

使用场景: 在某个场景下,你会比TypeScript更加明确知道某个值是什么类型的时候,你就可以指定这个值是什么类型,这种操作就称为类型断言

非空断言(!): 当某个值它的类型中存在null或者undefined这种类型的时候,TypeScript可能会在检测的时候会出现报错(例如获取页面已经存在的节点的时候),这时候可以在表达式的末尾加上用来排除null和undefined带来的影响(在你非常确定的时候才可以这样做,不然会给你带来很多问题)

const div = document.getElementById('app')! // 不加这个!可能会在你下面使用这个节点的时候提醒你这个div可能是一个null

常量断言(as): 可以使TypeScript推导出来的类型更加精确

let a = 1 // 虽然这里没有写变量a的类型,但是TypeScript会自动进行推导得出这个变量a
          // 是一个number的类型,但是如果需要这个类型的范围进一步缩小的话就需要使
          // 用到常量断言了

let a = 1 as const // 此时这里的变量a的类型就精确到1了

类型断言: 只要两个类型之间存在兼容关系,那么这两个类型就可以相互断言(与父子关系无关)

interface O1 {
    name: string
}

interface O2 {
    name: string
    age: number
}

let o1: O1 = {
    name: "abc"
}

let o2: O2 = {
    name: "abc"
    age: 18
}

// 断言的两种表示方式
(<O2>o1).age 
(o1 as O2).age

TypeScript的类型检查只能检查可以立即看到的代码的错误,如果像需要函数执行在浏览器中看到的TypeScript是检查不了的...

TypeScript的泛型

定义: 泛型在TypeScript中表示一个不确定的类型,它在使用的时候需要先去声明再去使用,并且使用的时候一般使用一个大写字母表示。

// 定义一个函数,返回输入的值
function log<x>(value: x): x { // 为了适配,所以这里函数的参数和返回值不能只
                               // 定义单一的类型,所以为了解决这个问题,可以
                               // 定义一个泛型x,用它表示函数的参数和返回值,
                               // 那么函数的参数和返回值的类型就可以在使用的时
                               // 候指定了
    return value
}
log<string>("") // 函数的参数类型是字符串,其返回值也是字符串

如果需要在使用泛型的时候使用断言,那么就不能显的使用泛型,因为同一个泛型在不同的位置,只要有一个位置确定了类型,那么其他位置的类型也会确定下来

function log<x>(value: x): x {
    return value
}

log(1 as const) // 在这里如果上面显式指定的泛型<x>不去掉的话那log函数的参数和返回值
                // 的类型就都是number了,因为显式的通过泛型<>传递类型的优先级要高于
                // 类型推导。

泛型类型的确定:

function log<x>(value: x): x {
    return value
}

// 显式传递类型
log<123>(123) // 因为前面已经将泛型的类型确定为123,那么按照规定,
              // 后面的参数和返回值的类型没得选,只有123
              
// 隐式类型推导
log(123) // 这里虽然没有指定泛型的类型,但是由于参数123的类型是number,
         // 那么一条龙服务,泛型的类型和函数返回值的类型也是123了
         
// 优先级
log<string>('123' as const) // 这里函数的参数和返回值的类型是string,
                            // 所以显式传递的类型优先级更高

如果在函数中使用了泛型这个函数称为泛型函数,类中使用就称为泛型类….以及接口中使用了泛型就称为泛型接口(在一个接口中存在若干个属性的类型是不确定的类型的时候就需要使用泛型接口了)

// 泛型接口:(以人养宠物为例)

interface Icat {
    type: "cat"
} // 这里定义了一个宠物猫的类型

interface Iperson<P = null> { // 泛型是可以指定默认值的
    name: string
    age: number
    pet: P // 因为每个人养的宠物类型不一样,所以这里的类型应该是一个变量,使用
           // 泛型来表示
} // 这里定义了一个人的类型

let zcy: Iperson<Icat> { // 然后在使用的时候需要指明所养宠物的类型,这里
                         // 就需要指明养的是猫,如果养的是狗的话那么就需要
                         // 声明是狗了....
    name: "zhnagsan"
    age: 20
    pet: {
        type: cat
    }
}
// 泛型类:
class Person<T, U> {
    name: T // 这个class的实例属性,需要先声明才能够去使用
    age: U
    constructor(name: T, age: U) {
        this.name = name
        this.age = age
    }
    echo(): T {
        return this.name 
    }
}

let person = new Person<string, number>("test", 13) // 显式指定泛型类型
let person = new Person("test", 13) // 也可以不给,因为它会隐式的进行转换
person.echo()

泛型约束:(extends) 将泛型的范围收窄,跟断言的作用类似

// 定义一个函数,函数的参数类型和返回值类型保持一致,并且其类型只能是string、
// number、boolean三种类型(不使用函数重载)

function print<T extends string | boolean | number>(params: T): T {
    return params
}

TypeScript的类型运算符

类型取值运算符([]) :使用的话跟取数组中的值使用索引类似

interface A {
    name: string
    age: number
}

type NameA = A["name"] // string
type AgeA = A["age"] // number
type AU = A[keyof A] // string | number
type B = [string, number]

type B0 = B[0] // string
type BU = B[number] // string | number --> 一次性取出会得到联合类型

keyof: 可以将某种类型的所有键获取到,并将这些类型转换为一个联合类型,它操作的是一个类型

interface c {
    a: number
    b: string
    c: boolean
}

type C = keyof c // 这里C的类型就是:number | string | boolean

typeof: 能够获取ts推导出来的类型,它操作的是变量,是一个值

let d = {
    name: "zhangsan"
    age:20
}

type D = typeof d // 这里的D就会得到name的类型是string,age的类型是number

// D的类型如下
D {
    name: string
    age: number
}

// 获取d中属性的联合类型:
type DU = typeof d[keyof typeof d] // string | number
// 定义一个函数,这个函数有两个参数,第一个参数是一个对象,第二个参数是该对象的
// 一个属性,函数返回值是该对象对应属性的属性值

// 第一个参数需要被限制成为一个对象的类型,第二个参数被限制成只能是该对象的属性名
function getValue<D extends {[key: string]: any}, K extends keyof D>(data: D, key: K): D[k] {
    return data[key]
}

getValue({a: 1}, "a") // 1

in: 遍历联合类型

type E = "a" | "b" | "c"

type F = {
    [key in E]: string
}

// F的类型:
type F = {
    a: string
    b: string
    c: string
}

自定义工具类型: 类似于js中的函数,自己定义类型的转换

// 例如:定义一个工具类型,将传进来的类型转换为所有成员拥有只读属性
type RO<T extends {[key: string]: any}> = {
    readonly [key in keyof T]: T[key]
}

extends: 判断两个类型的兼容性

type isTrue<A, B> = A extends B ? true : false

type H = isTrue<number, 1> // false
type I = isTrue<1, number> // true
// <--> A extends B <=> 子类型 extends 父类型
// 判断两个类型是否全等
type isTrues<A, B> = A extends B ? (B extends A ? true : false) : false

type H = isTrue<number, 1> // false
type H = isTrue<number, number> // true

infer: 可以从某个复合类型中提取一部分类型出来

// 获取函数的返回值的类型

// 传递一个类型进来,当这个类型满足函数类型的时候,函数的返回值的类型会被infer这个
// 关键字所接住,在infer关键字后面定义一个变量,被infer接住的这个类型就会被赋值给
// 变量R然后返回回来,如果不满足就会返回never类型
type fn<T> = T extends (...args: any[]) => infer R ? R : never

TypeScript的类型守卫

使用情况: 当类型需要被细分、需要收窄的时候使用

typeof关键字:

// 定义一个函数,函数参数类型为string或者是number,当参数为字符串的时候返回
// 字符串的长度,当参数为数字的时候返回参数

function echo(arg: string): number
function echo(arg: number): number
function echo(arg: string | number): number {
    if(typeof arg === "string") {
        return arg.length
    } else {
        return arg
    }
} // 跟函数重载有点类似,只不过在重载的基础之上增加了一些判断

instanceof关键字:

function echo(arg: unknown): number {
    if(arg instanceof Function) {
        return arg.length
    } else if(arg instanceof String) {
        return arg
    } else {
        return 0
    }
} 

in关键字(这里表示存在的意思,不是遍历):

function echo(params: A | B): number {
    if("a" in params) {
        console.log(存在"a")
    } else {
        console.log(不存在"a")
    }
} 

is关键字:

function echo(arg: A | B): arg is A { // 判断参数arg是不是A类型,返回值应该是一个布尔值
    return "a" in params
} 

TypeScript的全局类型和局部类型

全局类型: 当一个.ts文件中不存在import或者是export关键字的时候,那么这个文件就是一个全局的文件,这个文件里面所有的变量和函数在整个项目中任意的.ts文件中都可以使用

局部类型: 如果一个文件中存在import或者export关键字的时候那么这个文件就是一个局部文件,如果这个里面需要将变量供给外面的时候需要使用export导出,同理,如果需要使用到局部文件中的变量的时候需要使用import关键字进行导入才可以使用

import { A } from './1.ts' // 导入1.ts文件中的变量A
import type { A } from './1.ts' // 导入1.ts文件中的A类型(需要使用type关键字)

export default A // 默认导出变量A,如果需要导出的变量只有一个的话需要使用export default
export { A } // 常规导出变量A

decale global {} // 在局部文件中可以在decale定义全局的类型、变量、函数等(在平时定义的同时在开头添加一个decale就可以)

可以专门写一些.ts文件用来存放类型声明,类型声明是没有变量名和值的,就单纯只有类型声明而已,而全局的变量声明是包含变量名和变量类型的,这些都不包括实现。

原文链接:https://juejin.cn/post/7240636255972360248 作者:梦满枝头月初盈

(0)
上一篇 2023年6月5日 上午10:47
下一篇 2023年6月5日 上午10:58

相关推荐

发表回复

登录后才能评论