diff --git a/Axios_Typescript_Encapsulation/README.md b/Axios_Typescript_Encapsulation/README.md new file mode 100644 index 0000000..55fb77f --- /dev/null +++ b/Axios_Typescript_Encapsulation/README.md @@ -0,0 +1,7 @@ +# 用 TypeScript 封装 Axios + +` https://juejin.cn/post/7071518211392405541#heading-14` + +## 技术栈 +- TypeScipt +- Axios \ No newline at end of file diff --git a/Axios_Typescript_Encapsulation/package.json b/Axios_Typescript_Encapsulation/package.json new file mode 100644 index 0000000..af595d0 --- /dev/null +++ b/Axios_Typescript_Encapsulation/package.json @@ -0,0 +1,16 @@ +{ + "name": "axios_typescript_encapsulation", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^0.26.1", + "typescript": "^4.6.3" + } +} diff --git a/Axios_Typescript_Encapsulation/src/example/index.ts b/Axios_Typescript_Encapsulation/src/example/index.ts new file mode 100644 index 0000000..204a9e6 --- /dev/null +++ b/Axios_Typescript_Encapsulation/src/example/index.ts @@ -0,0 +1,14 @@ +import Request from "../lib/request"; + +const AxiosTs = new Request({ + baseURL: "www.baidu.com", + timeout: 1000 * 60 * 5, + interceptors: { + requestInterceptors: (config) => { + return config; + }, + responseInterceptors: (result) => { + return result; + }, + }, +}); diff --git a/Axios_Typescript_Encapsulation/src/index.ts b/Axios_Typescript_Encapsulation/src/index.ts new file mode 100644 index 0000000..f7effb8 --- /dev/null +++ b/Axios_Typescript_Encapsulation/src/index.ts @@ -0,0 +1,3 @@ +import Request from "./lib/request"; + +export default Request; diff --git a/Axios_Typescript_Encapsulation/src/lib/request.ts b/Axios_Typescript_Encapsulation/src/lib/request.ts new file mode 100644 index 0000000..955b6a1 --- /dev/null +++ b/Axios_Typescript_Encapsulation/src/lib/request.ts @@ -0,0 +1,129 @@ +import axios from "axios"; +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; +import type { + RequestConfig, + RequestInterceptors, + CancelRequestSource, +} from "../type"; + +class Request { + // axios 实例 + instance: AxiosInstance; + // 拦截器 + interceptorsObj?: RequestInterceptors; + // 取消请求方法合集 + cancelRequestSourceList?: CancelRequestSource[]; + // 请求 URL + requestUrlList?: string[]; + + // 拦截器的执行顺序为实例请求 → 类请求 → 实例响应 → 类响应 + + constructor(config: RequestConfig) { + this.instance = axios.create(config); + this.interceptorsObj = config.interceptors; + + // 类请求 + this.instance.interceptors.request.use( + (axiosConfig: AxiosRequestConfig) => { + return axiosConfig; + }, + (err: any) => err + ); + + // 实例响应 + this.instance.interceptors.request.use( + this.interceptorsObj?.requestInterceptors, + this.interceptorsObj?.requestInterceptorsCatch + ); + + this.instance.interceptors.response.use( + this.interceptorsObj?.responseInterceptors, + this.interceptorsObj?.responseInterceptorsCatch + ); + + // 类响应 + this.instance.interceptors.response.use( + (axiosResult: AxiosResponse) => { + return axiosResult.data; + }, + (err: any) => err + ); + } + + request(config: RequestConfig): Promise { + return new Promise((resolve, reject) => { + // 单个请求设置拦截器 + if (config.interceptors?.requestInterceptors) { + config = config.interceptors.requestInterceptors(config); + } + + const url = config.url; + if (url) { + // 存入 请求URL + this.requestUrlList?.push(url); + config.cancelToken = new axios.CancelToken((cancel) => { + // 存入 取消方法 + this.cancelRequestSourceList?.push({ + [url]: cancel, + }); + }); + } + + this.instance + .request(config) + .then((res) => { + if (config.interceptors?.responseInterceptors) { + res = config.interceptors.responseInterceptors(res); + } + + resolve(res); + }) + .catch((err) => { + reject(err); + }) + .finally(() => { + url && this.delUrl(url); + }); + }); + } + + cancelAllRequest() { + this.cancelRequestSourceList?.forEach((eachItem) => { + const key = Object.keys(eachItem)[0]; + eachItem[key](); + }); + } + + cancelRequest(url: string | string[]) { + if (typeof url === "string") { + // 取消单个请求 + const sourceIndex = this.getSourceIndex(url); + sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url](); + } else { + // 存在多个需要取消请求的地址 + url.forEach((u) => { + const sourceIndex = this.getSourceIndex(u); + sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u](); + }); + } + } + + private delUrl(url: string) { + const urlIndex = this.requestUrlList?.indexOf(url); + const sourceIndex = this.getSourceIndex(url); + + urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1); + sourceIndex !== -1 && + this.cancelRequestSourceList?.splice(sourceIndex as number, 1); + } + + private getSourceIndex(url: string): number { + return this.cancelRequestSourceList?.findIndex( + (findItem: CancelRequestSource) => { + return Object.keys(findItem)[0] === url; + } + ) as number; + } +} + +export default Request; diff --git a/Axios_Typescript_Encapsulation/src/type/index.ts b/Axios_Typescript_Encapsulation/src/type/index.ts new file mode 100644 index 0000000..66ceb5c --- /dev/null +++ b/Axios_Typescript_Encapsulation/src/type/index.ts @@ -0,0 +1,17 @@ +import type { AxiosRequestConfig, AxiosResponse } from "axios"; + +export interface RequestInterceptors { + requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig; + requestInterceptorsCatch?: (err: any) => any; + + responseInterceptors?: (config: T) => T; + responseInterceptorsCatch?: (err: any) => any; +} + +export interface RequestConfig extends AxiosRequestConfig { + interceptors?: RequestInterceptors; +} + +export interface CancelRequestSource { + [index: string]: () => void; +} diff --git a/Axios_Typescript_Encapsulation/tsconfig.json b/Axios_Typescript_Encapsulation/tsconfig.json new file mode 100644 index 0000000..7624955 --- /dev/null +++ b/Axios_Typescript_Encapsulation/tsconfig.json @@ -0,0 +1,101 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/Axios_Typescript_Encapsulation/yarn.lock b/Axios_Typescript_Encapsulation/yarn.lock new file mode 100644 index 0000000..6426984 --- /dev/null +++ b/Axios_Typescript_Encapsulation/yarn.lock @@ -0,0 +1,20 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +axios@^0.26.1: + version "0.26.1" + resolved "https://registry.npmmirror.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + +follow-redirects@^1.14.8: + version "1.14.9" + resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== + +typescript@^4.6.3: + version "4.6.3" + resolved "https://registry.npmmirror.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==