05学习TypeScript

1.TypeScript模块使用

TypeScript中最主要使用的模块化方案就是ES Module

1.1非模块

如果你有一个文件,现在没有任何import或者export,但是你希望它被作为模块处理添加这行代码:export {},把文件改成一个没有导出任何内容的模块,解决命名冲突

1.2内置类型导入

使用type前缀,表明被导入的是一个类型,让一个非TypeScript编译器比如Babel,swc或者esbuild知道什么样的导入可以被安全移除

type.ts

export type IDType = number | string

export interface IPerson{
    name:string
    age:number
}

index.ts

// 导入的是类型,推荐在类型的前面加上type关键
// 作用:只有代码编写时做类型检测需要,代码编译后不需要可以移除掉
// import { type IDType, type IPerson } from "./utils/type";
import type { IDType,  IPerson } from "./utils/type";

const id: IDType = 111
const p: IPerson = { name: "jack", age: 18 }

2.TypeScript命名空间(了解)

TypeScript有它自己的模块格式,名为namespaces,它在ES模块标准之前出现

  • 命名空间在TypeScript早期时,称之为内部模块,目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题;
  • 虽然命名空间没有被废弃,但是由于ES模块已经拥有了命名空间的大部分特性,因此更推荐使用ES模块,这样才能与JavaScript的(发展)方向保持一致。
export namespace price {
  export function format(price: string) {
    return "¥" + price
  }

  export const name = "price"
}

export namespace date {
  export function format(dateString) {
    return "2022-10-10"
  }

  const name = "date"
}
// 使用命名空间中的内容
import { price, date } from "./utils/format";

price.format("1111")
date.format("22222")

3.内置声明文件的使用

const imageEl = document.getElementById("image") as HTMLImageElement;
HTMLImageElement类型涉及到typescript对类型的管理和查找规则

另外的一种文件.d.ts 文件,它是用来做类型的声明(declare),称之为**类型声明(Type Declaration)或者类型定义(TypeDefinition)**文件,仅仅用来做类型检测,告知typescript我们有哪些类型;
举个例子:
前面编写的type.ts文件重命名为type.d.ts去掉export用来类型声明(不包含业务逻辑代码),并且变为全局文件
那么typescript会在哪里查找我们的类型声明呢?

  • 内置类型声明; VScode内置好的
  • 外部定义类型声明; 一般axios / React的第三方库
  • 自己定义类型声明; 自己编写的.d.ts类型声明文件

3.1 内置类型声明

内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件;

  • 包括比如Function、String、Math、Date等内置类型;
  • 也包括运行环境中的DOM API,比如Window、Document等;

TypeScript使用模式命名这些声明文件lib.[something].d.ts
内置类型声明通常在我们安装typescript的环境中会带有的:link

3.2 搭建webpack环境,将ts代码运行到浏览器上

  1. 进入需要操作的目录,打开集成终端执行npm init,初始化package.json文件
  2. 新建src文件夹并在src文件夹里新建index.ts文件,
  3. 在集成终端执行npm install webpack webpack-cli -D,在开发环境(devDependencies)安装webpack
  4. 需要操作的目录新建webpack.config.jswebpack配置文件,终端执行npm install ts-loader -D,npm install html-webpack-plugin -D npm install webpack-dev-server -D安装webpack服务,开启本地服务 ,webpack.config.js文件会用到ts编译和html模板和webpack-dev-server本地服务,当前目录新建index.html模板

webpack.config.js:

const path = require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    mode: "development",
    entry: "./src/index.ts",
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: "bundle.js"
    },
    resolve: {
        //默认有".js",".cjs",".json",新加的.ts防止覆盖都写
        extensions: [".ts", ".js", ".cjs", ".json"]
    },
    devServer: {
        //默认为true
        hot: true,
        //指定端口
        port: 8888,
        // host:"0.0.0.0",
        //浏览器自动打开
        open: true
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                loader: "ts-loader"
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./index.html"
        })
    ]
}
  1. package.json文件里添加"scripts": {"serve": "webpack serve" } ,ts-loader需要依赖tsconfig.json文件,终端执行tsc --init生成tsconfig.json文件,终端执行npm run serve运行
  2. 搭建完webpack环境后,编写代码运行ts代码:src目录新建utils文件夹,里面新建math.ts文件export sum函数,index.ts使用console.log(sum(10,40))

3.3 内置声明的环境

我们可以通过target和lib来决定哪些内置类型声明是可以使用的:

  • 例如,startsWith字符串方法只能从称为ECMAScript 6的 JavaScript 版本开始使用;
    tsconfig.json
"target": "es2016",  代表ES7
// "lib": [“DOM”], 

一般不用tsc编译,babel会根据.browserslistrc文件里面的内容做哪些转化

4.第三方库声明的文件

第三方包如果没有对应类型声明文件应该如何进行处理:

  1. 包本身包含声明文件 axios
  2. 没有包含,但在官方DefineTypes库中包含声明文件
    npm i @types/react -D
  3. 自己没有声明,DefineTypes库也没有
    手写自定义声明文件

5.编写自定义声明文件

什么情况下需要自己来定义声明文件呢?
情况一︰我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件; 比如lodash
情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;

// 需要编写类型声明 index.ts
console.log(whyName, whyAge, whyHeight)
console.log(foo("why"))

const p = new Person("kobe", 30)
console.log(p.name, p.age)

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TSDemo</title>
</head>

<body>
    <script>
        const whyName = "why"
        const whyAge = 18
        const whyHeight = 1.88

        function foo(bar) {
            return "hello world"
        }

        function Person(name, age) {
            this.name = name
            this.age = age
        }
    </script>
</body>

</html>

types->why.d.ts

// 为自己的 变量/函数/类 定义类型声明
declare const whyName: string
declare const whyAge: number
declare const whyHeight: number

declare function foo(bar: string): string

declare class Person {
  constructor(public name: string, public age: number)
}

5.1 declare声明模块:

我们也可以声明模块,比如lodash模块没有安装npm install @types/lodash -D的情况,可以自己来声明这个模块:

types文件夹why.d.ts文件

declare module "lodash" {
  export function join(...args: any[]): any
}

导入并使用:import _ from “loadsh” _.join([“a”,“b”])
声明模块语法:declare module '模块名' {}

  • 声明模块的内部,我们可以通过export 导出对应库的类、函数等;

5.2 declare 声明文件

在某些情况下,我们也可以声明文件:

  • 比如在开发vue的过程中,默认是不识别我们的.vue文件的,那么需要对其进行文件的声明;
  • 比如在开发中我们使用了jpg这类图片文件,默认typescript也是不支持的,也需要对其进行声明;

webpack.config.js:
webpack配置加载图片连接链接.

// index.ts 图片文件的使用
import KobeImage from "./img/kobe02.png"//默认ts文件不识别,声明文件模块后才识别
const imgEl = document.createElement("img")
imgEl.src = KobeImage
document.body.append(imgEl)
// types文件夹里的why.d.ts文件
// 声明文件模块
declare module "*.png"
declare module "*.jpg"
declare module "*.jpeg"
declare module "*.svg"

5.3 declare 命名空间

比如我们在index.html中直接引入了jQuery:
    CDN地址: https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>

我们可以进行命名空间的声明:

// 声明命名空间
declare namespace $ {
    export function ajax(settings: any): any
}

在index.ts中就可以使用了:

// jquery
$.ajax({
    url: "http://codercba.com:8000/home/multidata",
    success: function (res: any) {
        console.log(res)
    }
})

6.tsconfig配置文件解析

6.1 tsconfig.json文件作用:

    作用一(主要的作用)︰让TypeScript Compiler在编译的时候,知道如何去编译TypeScript代码和进行类型检测;

  • 比如是否允许不明确的this选项,是否允许隐式的any类型;
  • 将TypeScript代码编译成什么版本的JavaScript代码;

    作用二:让编辑器(比如VSCode)可以按照正确的方式识别TypeScript代码;

  • 对于哪些语法进行提示、类型错误检测等等;

JavaScript项目可以使用jsconfig,json文件,它的作用与tsconfig,json 基本相同,只是默认启用了一些JavaScript相关的编译选项。

6.2 tsconfig.json文件配置

  • tsconfig.json在编译时如何被使用呢?
    • 在调用tsc命令并且没有其它输入文件参数时,编译器将由当前目录开始向父级目录寻找包含tsconfig 文件的目录。
    • 调用tsc命令并且没有其他输入文件参数,可以使用–project(或者只是-p)的命令行选项来指定包含了tsconfig.json的目录;
    • 当命令行中指定了输入文件参数, tsconfig.json文件会被忽略;
  • webpack中使用ts-loader进行打包时,也会自动读取tsconfig文件,根据配置编译TypeScript代码。
  • tsconfig.json文件包括哪些选项呢?
    • tsconfig.json本身包括的选项非常非常多,我们不需要每一个都记住;
    • 可以查看文档对于每个选项的解释: tsconfig.json选项解释链接
    • 当我们开发项目的时候,选择TypeScript模板时,tsconfig文件默认都会帮助我们配置好的;
  • 接下来我们学习一下哪些重要的、常见的选项。
tsconfig.json顶层选项
{
  "compilerOptions": {
  },
  // "files": [] 指定哪些ts文件需要进行编译 当项目中文件比较少时,可以使用这个选项
  // "include": ["./src/**/*.ts", "./types/*.d.ts"] 编写一个数组,用于指定在项目中包括哪些文件默认匹配的是根目录下所有的文件
  // "exclude": []        //编写一个数组,用于指定从include中排除哪些文件
}

常见的选项:vuecli生成的一些配置选项

{
"compileroptions":{
	//目标代码esnext和ES版本保持一致,最终是靠babel来进行转换,.browserslistrc
	"target": "esnext",  
	//最终生成代码使用的模块化,babel来进行转换
	"module": "esnext",
	//ts中进行严格的类型检测
	"strict": true,
	"allowJs": false,//不允许编写js代码
	"noImplicitAny": false,//不允许模糊隐式的any
	//jsx的处理方式(保留原有的jsx格式),留给babel
	"jsx": "preserve",
	//是否帮助导入一些需要的功能模块
	"importHelpers": true,
	//按照node的模块解析规则
	//https://www.typescriptlang.org/docs/handbook/module-resolution.html#module-resolution-strategies
	"moduleResolution": "node",
	//跳过对整个库进行类似检测,·而仅仅检测你用到的类型
	"skipLibCheck": true,
	//可以让es  module·和·commonjs相互调用
	"esModuleInterop": true,
	// 允许合成默认模块导出
	// import * as  react from  'react': false
	// import react from  'react': true
	"allowSyntheticDefaultImports":  true,
	//是否要生成sourcemap文件
	"sourceMap": true,
	//文件路径在解析时的基本url
	"baseUrl":".",
	//指定types文件需要加载哪些(默认是都会进行加载的)
	// "types": [
	//	  "webpack-env"
	// ],
	//路径的映射设置,类似于webpack中的alias
	"paths": {
		"@/*":["src/*"]
	}// 指定我们需要使用到的库(也可以不配置,直接根据target来获取)
	"lib" : ["esnext" , "dom" , "dom.iterable",  "scripthost"],
	},
	"include" : [],
	"exclude" : ["node_modules"]
}

7 TS中axios封装

7.1 HYRequest类的基本封装

src\service\request\index.ts

import axios from "axios"
import type {AxiosInstance,AxiosRequestConfig} from "axios"

class HYRequest{
    instance:AxiosInstance
    
    // request实例 对应axios的实例
    constructor(config:AxiosRequestConfig){
        this.instance = axios.create(config)
    }

    // 封装网络请求的方法
    request(config:AxiosRequestConfig){
        return this.instance.request(config)
    }

    get(){

    }

    post(){
        
    }
}

export default HYRequest

src\service\modules\home.ts调用封装的网络请求request方法

import hyRequest from "../index";

//发送网络请求
// hyRequest.post
hyRequest.request({
    url:"/home/multidata"
}).then(res=>{
    console.log(res.data);
})

src\service\index.ts

import { BASE_URL, TIME_OUT } from "./config";
import HYRequest from "./request";

//可以根据不同url创建很多实例
// const hyRequest1 = new HYRequest()
// const hyRequest2 = new HYRequest()
const hyRequest = new HYRequest({
    baseURL:BASE_URL,
    timeout:TIME_OUT
})


export default hyRequest

src\service\config\index.ts

export const BASE_URL = "http://codercba.com:8000"
export const TIME_OUT = 10000

7.2 为全局或者单独的请求(请求爱彼迎接口)添加拦截器的封装

npm install axios@next 加上next装的是:axios@1.2.0-alpha.1版本,axios1.2.4后的版本会报错

import type { HYRequestConfig } from "./type"

constructor(config: HYRequestConfig) {
        //每个instance实例都添加拦截器
        this.instance.interceptors.request.use((config) => {
            /* config.headers = {
                token:"xxxx"
            } */
            //  token/loading
            console.log("全局请求成功的拦截");
            return config
        }, (error) => {
            // Do something with request error
            console.log("全局请求失败的拦截");
            return Promise.reject(error);
        })

        this.instance.interceptors.response.use(res => {
            console.log("全局响应成功的拦截");
            return res
        }, error => {
            console.log("全局响应失败的拦截");
            return Promise.reject(error);
        })

        // 针对特定的hyRequest实例添加拦截器
        if (config.interceptors) {  //类型缩小
            this.instance.interceptors.request.use(
                config.interceptors.requestSuccessFn,
                config.interceptors.requestFailureFn
            )
            this.instance.interceptors.response.use(
                config.interceptors.responseSuccessFn,
                config.interceptors.responseFailureFn
            )
        }

    }
import type { AxiosRequestConfig, AxiosResponse } from "axios"

// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors {
    requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
    requestFailureFn?: (err: any) => any
    responseSuccessFn?: (res: AxiosResponse) => AxiosResponse
    responseFailureFn?: (err: any) => any
}

export interface HYRequestConfig extends AxiosRequestConfig {
    interceptors?: HYInterceptors
}
import { BASE_URL, TIME_OUT } from "./config";
import HYRequest from "./request";

//可以根据不同url创建很多实例
const hyRequest = new HYRequest({
    baseURL: BASE_URL,
    timeout: TIME_OUT
})

// 针对爱彼迎的接口进行拦截,其他的不拦截
export const hyRequest2 = new HYRequest({
    baseURL: "http://codercba.com:1888/airbnb/api",
    timeout: 8000,

    interceptors: {
        requestSuccessFn: (config) => {
            console.log("爱彼迎的请求成功的拦截")
            return config
        },
        requestFailureFn: (err) => {
            console.log("爱彼迎的请求失败的拦截")
            return err
        },
        responseSuccessFn: (res) => {
            console.log("爱彼迎的响应成功的拦截")
            return res
        },
        responseFailureFn: (err) => {
            console.log("爱彼迎的响应失败的拦截")
            return err
        }
    }

})

export default hyRequest

7.3 HYRequest类的单次请求拦截

针对不同需求进行不同拦截:
①针对单次请求进行拦截
②实例进行拦截
③全局进行拦截

共三种拦截器

hyRequest2.request({ 
    url:"/home/highscore",
    interceptors:{
        requestSuccessFn:(config)=>{
            console.log("/home/highscore请求成功的拦截");
            return config
        },
        responseSuccessFn:(res)=>{
            console.log("/home/highscore响应成功的拦截");
            return res
        }
    }
}).then(res=>{
    console.log(res);
    
})
		this.instance.interceptors.response.use(res => {
            return res.data
        }, error => {
        })
	// 封装网络请求的方法
    request(config: HYRequestConfig) {
        // 单次请求的成功拦截处理
        if (config.interceptors?.requestSuccessFn) {
            config = config.interceptors.requestSuccessFn(config)
        }

        // 返回Promise
        return new Promise((resolve, reject) => {
            this.instance.request(config).then(res => {
                // 单次响应的成功拦截处理
                if (config.interceptors?.responseSuccessFn) {
                    res = config.interceptors.responseSuccessFn(res)
                }
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        })
    }

7.4 返回结果的类型处理unknown问题<泛型>

// 封装网络请求的方法
    request<T=any>(config: HYRequestConfig) {
        // 单次请求的成功拦截处理
        if (config.interceptors?.requestSuccessFn) {
            config = config.interceptors.requestSuccessFn(config)
        }

        // 返回Promise
        return new Promise<T>((resolve, reject) => {
            this.instance.request<any, T>(config).then(res => {
                // 单次响应的成功拦截处理
                if (config.interceptors?.responseSuccessFn) {
                    // res = config.interceptors.responseSuccessFn(res)
                }
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        })
    }
interface IHomeData {
    data: any,
    returnCode: string,
    success: boolean
}
hyRequest.request<IHomeData>({
    url: "/home/multidata"
}).then(res => {
    console.log(res.data, res.success, res.returnCode);
})
/* 
res是unknown类型在上面进行任意的操作都是非法的
new Promise<string>((resolve,reject)=>{
    resolve("hahah")
}).then(res=>{
    console.log(res.length);
})
*/
interface IHighScoreData {
    list: any[],
    subtitle: string,
    title: string
    type: string,
    _id: string
}
hyRequest2.request<IHighScoreData>({
    url: "/home/highscore",
}).then(res => {
    console.log(res.list, res.subtitle, res.title);
})

7.5 接口类型中的泛型处理

import type { AxiosRequestConfig, AxiosResponse } from "axios"

// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {
    requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
    requestFailureFn?: (err: any) => any
    responseSuccessFn?: (res: T) => T
    responseFailureFn?: (err: any) => any
}

export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
    interceptors?: HYInterceptors<T>
}
request<T=any>(config: HYRequestConfig<T>) {
        // 返回Promise
        return new Promise<T>((resolve, reject) => {
            this.instance.request<any, T>(config).then(res => {
                // 单次响应的成功拦截处理
                if (config.interceptors?.responseSuccessFn) {
                    res = config.interceptors.responseSuccessFn(res)
                }
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        })
    }

7.6 其他请求方式的添加

	get<T = any>(config: HYRequestConfig<T>) {
        return this.request({ ...config, method: "GET" })
    }
    post<T = any>(config: HYRequestConfig<T>) {
        return this.request({ ...config, method: "POST" })
    }
    delete<T = any>(config: HYRequestConfig<T>) {
        return this.request({ ...config, method: "DELETE" })
    }
    patch<T = any>(config: HYRequestConfig<T>) {
        return this.request({ ...config, method: "PATCH" })
    }

7.7 axios封装总结

两个难点:
1.拦截器进行精细控制

  • 全局拦截器

  • 实例拦截器

  • 单次请求拦截

2.响应结果的类型处理(泛型)

写在最后

本篇文章到这里就结束了,如果文章对你有用,可以三连支持一下,如果文章中有错误或者说你有更好的见解,欢迎指正~
代码地址: lemonDin/learn_typescript(github.com).

PS:代码和思路来自王红元(coderwhy)老师,在其中做个总结

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿安逸的少少

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值