使用 Typescript 封装 Axios

news2024/9/22 1:12:18

对 axios 二次封装,更加的可配置化、扩展性更加强大灵活

通过 class 类实现,class 具备更强封装性(封装、继承、多态),通过实例化类传入自定义的配置

创建 class

严格要求实例化时传入的配置,拥有更好的代码提示

/**
 * @param {AxiosInstance} axios实例类型
 * @param {AxiosRequestConfig} axios配置项类型
 */
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
​
class Http {
    axios: AxiosInstance
    constructor(config: AxiosRequestConfig) {
        // 创建一个实例 axios.create([config])
        this.axios = axios.create(config)
  }
}
​
// 每实例化一个 axios 时,都是不同的 axios 示例,互不干扰
new Http({
    baseURL:'qq.com';
    timeout:60 * 1
});
​
new Http({
    baseURL:'web.com'
}); 
axios.create([config])
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
}); 

AxiosRequestConfig 的类型注解

export interface AxiosRequestConfig<D = any> {
  url?: string;
  method?: Method | string;
  baseURL?: string;
  transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
  transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
  headers?: AxiosRequestHeaders;
  params?: any;
  paramsSerializer?: (params: any) => string;
  data?: D;
  timeout?: number;
  timeoutErrorMessage?: string;
  withCredentials?: boolean;
  adapter?: AxiosAdapter;
  auth?: AxiosBasicCredentials;
  responseType?: ResponseType;
  responseEncoding?: responseEncoding | string;
  xsrfCookieName?: string;
  xsrfHeaderName?: string;
  onUploadProgress?: (progressEvent: any) => void;
  onDownloadProgress?: (progressEvent: any) => void;
  maxContentLength?: number;
  validateStatus?: ((status: number) => boolean) | null;
  maxBodyLength?: number;
  maxRedirects?: number;
  beforeRedirect?: (options: Record<string, any>, responseDetails: {headers: Record<string, string>}) => void;
  socketPath?: string | null;
  httpAgent?: any;
  httpsAgent?: any;
  proxy?: AxiosProxyConfig | false;
  cancelToken?: CancelToken;
  decompress?: boolean;
  transitional?: TransitionalOptions;
  signal?: AbortSignal;
  insecureHTTPParser?: boolean;
  env?: {
    FormData?: new (...args: any[]) => object;};
} 

封装 request(config)通用方法

/**
* axios#request(config)
* @param {*} config
* @returns {*}
*/
request(config: AxiosRequestConfig) {
    return this.axios.request(config)
} 

🏹 在 axios 中,request中的 config 参数与实例化时,axios.create(config)传入的参数是相同的,以下是 axios 方法具体参数

axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
axios.getUri([config]) 

封装-拦截器(单个实例独享)

拦截器的hooks,在请求或响应被 then 或 catch 处理前拦截处理

🛹在请求中,如携带token、loading动画、header配置...,都是一些公有的逻辑,所以可以写到全局的拦截器里面

🛵注意的是,可能存在某些项目请求,需要的公有的逻辑配置方式不一样,如 A项目:携带token、loading动画,B项目:携带token、header配置

🪂考虑到拦截方式不一样,固不能将 class 里的拦截器写si,所以,需要通过不同的 axios 实例化传入自定义的 hooks(拦截器)实现单个实例独享,扩展性更强

在上面实例化 Http(Axios) 时,仅仅传入 axios 约定好的 config(AxiosRequestConfig)

new Http({
    baseURL:'qq.com';
    timeout:60 * 1
}); 

经过分析考虑,需要在实例化 Http 时,可以传入更多自定义的 hooks,扩展 axios。但是,在实例化时,直接传入定义好的拦截器是不可行的 Why?

new Http({
    baseURL: 'qq.com',
    timeout: 60 * 1,
    hooks: {}, // ERROR ❌❌⭕⭕
    interceptor: () => {} // ERROR ⭕⭕❌❌
}) 

“hooks”不在类型“AxiosRequestConfig<any>”中,在上面 Http class 的 constructor 构造函数中,严格约束传入的参数应为AxiosRequestConfig类型(TS)

AxiosRequestConfig 中并不存在 hooks and interceptor类型的属性(AxiosRequestConfig 是由 axios 提供的一个类型注解)

🚀扩展 Http 自定义拦截器

AxiosRequestConfig 类型注解进行继承,使得在实例化时,可以传入扩展后的类型,定义:

🏜️IinterceptorHooks接口存储自定义的拦截器函数、

🏙️IHttpRequestConfig接口继承 AxiosRequestConfig,并扩展自定义拦截器的属性,属性类型为:IinterceptorHooks

🏜️`IinterceptorHooks`拦截器Hook接口类型
import type { AxiosResponse, AxiosRequestConfig } from 'axios'
/**
 * 拦截器的hooks,在请求或响应被 then 或 catch 处理前拦截
 * @param {beforeRequestInterceptor(?)} 发送请求之前拦截器
 * @param {requestErrorInterceptor(?)} 请求错误拦截器
 * @param {responseSuccessInterceptor(?)} 响应成功拦截器
 * @param {responseFailInterceptor(?)} 响应失败拦截器
 */
interface interceptorHooks {
    beforeRequestInterceptor: (config: AxiosRequestConfig) => AxiosRequestConfig
    requestErrorInterceptor: (error: any) => any
    responseSuccessInterceptor: (result: AxiosResponse) => AxiosResponse
    responseFailInterceptor: (error: any) => any
}
export type IinterceptorHooks = Partial<interceptorHooks> 

🌏Partial:Typescript 内置类型,将定义的类型注解全部变为可选的属性

🪂调用说明axios.interceptors.request.use( beforeRequestInterceptor , requestErrorInterceptor );

请求之前拦截器中(beforeRequestInterceptor),config 参数同样是与axios.create中的参数类型相同,为AxiosRequestConfig

注意:并不是IHttpRequestConfig

🏙️`IHttpRequestConfig` 类构造函数 config 接口类型
/**
 * 实例化Http类的配置项参数,继承于AxiosRequestConfig
 * @param {interceptors(?)} 拦截器Hooks
 * @param {loading} 请求loading
 * @param {...} 其它的配置项
 * @param {AxiosRequestConfig} axios原生的配置选
 */
export interface IHttpRequestConfig extends AxiosRequestConfig {interceptors?: IinterceptorHooks
} 

🛵使用说明

import { IHttpRequestConfig, IinterceptorHooks } from './types'

class Http {axios: AxiosInstanceinterceptors?: IinterceptorHooksconstructor(config: IHttpRequestConfig) {// 解构自定义的属性const { interceptors, ...AxiosRequestConfig } = configthis.axios = axios.create(AxiosRequestConfig)// 存储自定义拦截Hooks or 直接 use() 使用this.interceptors = interceptors// 传入自定义请求拦截器Hooksthis.axios.interceptors.request.use(this.interceptors?.beforeRequestInterceptor,this.interceptors?.requestErrorInterceptor)// 传入自定义响应拦截器Hooksthis.axios.interceptors.response.use(this.interceptors?.responseSuccessInterceptor,this.interceptors?.responseFailInterceptor)}
}

// 扩展后的实例化...⛵
 interceptors: { // ... 自定义的拦截Hooks beforeRequestInterceptor: (config: IHttpRequestConfig) => { const token = localStorage.getItem('token') if (token && config.headers) { config.headers.Authorization = token } return config }
 } 

封装-拦截器(所有实例共享)

无论存在多少个实例的 Http,所有实例都会共享的同一套拦截器,在 class 中固定的

class Http {axios: AxiosInstance// ...constructor(config: IHttpRequestConfig) {// ...单个实例独享拦截器处理🍳// 所有实例共享的拦截-请求拦截器🍟this.axios.interceptors.request.use(function (config) {// 在发送请求之前做些什么return config},function (error) {// 对请求错误做些什么return Promise.reject(error)})// 所有实例共享的拦截-响应拦截器this.axios.interceptors.response.use(function (response) {// 2xx 范围内的状态码都会触发该函数。// 对响应数据做点什么return response},function (error) {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error)})}
} 

封装-拦截器(单个请求独享)

在 axios 原生的 config(AxiosRequestConfig) 中,存在两个允许对请求or响应的数据进行转化处理:

// `transformRequest` 允许在向服务器发送前,修改请求数据
// 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
// 你可以修改请求头。
transformRequest: [function (data, headers) {
// 对发送的 data 进行任意转换处理
return data;
}],

// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对接收的 data 进行任意转换处理
return data;
}], 

由于transformRequest、transformResponse为 Axios 原生的API,所以尽量不去改变原有的 config API

🍟在需要不修改原生 Axios config 情况下,扩展单个请求独享的拦截器

🍳class 封装的方法:request(config: AxiosRequestConfig){}参数中不能再使用原生AxiosRequestConfig作为参数的注解

🫕前面提到过,request 中的 config 参数与实例化时传入的参数是相同的,所以在这里同样可以使用IHttpRequestConfig,作为 request 的参数注解

/**
* axios#request(config)
* @param {*} config
* @returns {*}
*/
request(config: AxiosRequestConfig) {
	return this.axios.request(config)
}

// ...改为 AxiosRequestConfig -> IHttpRequestConfig
request(config: IHttpRequestConfig) {// ... 在执行请求之前,执行单个请求独享的拦截器(?)return this.axios.request(config).then(	// ... )} 

具体代码实现:

// Http 封装的request🫕
 request(config: IHttpRequestConfig) { // 存在单个方法独享自定义拦截器Hooks(请求之前)🍀 if (config.interceptors?.beforeRequestInterceptor) { // 立即执行beforeRequestInterceptor方法,传入config处理返回新的处理后的config config = config.interceptors.beforeRequestInterceptor(config) } return this.axios .request(config) .then((result) => { // 存在单个方法独享自定义拦截器Hooks(响应成功)🍃 if (config.interceptors?.responseSuccessInterceptor) { // 立即执行beforeRequestInterceptor方法,传入config处理返回新的处理后的config result = config.interceptors.responseSuccessInterceptor(result) } return result }) .catch((error) => { // 存在单个方法独享自定义拦截器Hooks(响应失败)🌄 if (config.interceptors?.responseFailInterceptor) { // 立即执行beforeRequestInterceptor方法,传入config处理返回新的处理后的config error = config.interceptors.responseFailInterceptor(error) } return error })
 }

// 执行 request 方法,传入拦截器处理🌏,如:
axios.request({url: '/api/addUser',methed: 'POST',interceptors: {beforeRequestInterceptor:(config) => {// ...处理config数据return config},// ...请求错误处理不拦截responseSuccessInterceptor:(result) => {return result}}
}) 

注意:当存在单个方法独享自定义请求拦截器时,应当在发送请求之前立即执行 beforeRequestInterceptor 方法,而不是通过传入到 use() 回调,执行请求拦截方法处理完之后返回一个经过处理的 config,在传入到请求方法中,发送请求

其它的单个方法独享自定义响应拦截器一样

装修 Http class

返回经过

在响应数据时候(响应拦截器),Axios 为数据在外层包装了一层对象,后台返回的数据就存在于 result 的 data 中,所以需要对数据再一步的处理,扒开最外层的皮

注意:Axios 在外层包装的对象数据,其实在某些情况下是需要 result 中的一些属性数据的,并不仅仅需要 data,比如返回格式为文件数据中,需要 headers 中的一些数据对文件进行处理,命名等…

这里的扒皮处理,逻辑应当属于所有实例共享的一个拦截器,具体工具需要进行处理

// 所有实例共享的拦截-响应拦截器
this.axios.interceptors.response.use(function (response) {// 返回为文件流时,直接返回(文件需要单独处理)🪂if (['blob', 'arraybuffer'].includes(response.request.responseType)) {// if (response.headers['content-disposition']) {// let fileName = response.headers['content-disposition'].split('filename=')[1]// fileName = decodeURI(fileName)// return {// data: response.data,// fileName// }// }return response}// 根据业务码 or 状态码...进行判断// 扒皮🛵return response.data},function (error) {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error)}
) 

request 返回数据结构(DTO)

定义返回数据类型注解

// 最终数据类型注解
interface IDateType {[key: string]: any
}

// axios 返回的数据类型注解,IDateType === T
interface IDTO<T = any> {code: numberdata: Tmessage: stringstate: number[prop: string]: any
}

// 请求方法,传入注解
axios.request<IDTO<IDateType>>({url: '/api/addUser',methed: 'POST',
}) 

由于在所有实例共享的响应拦截器中,修改了返回的数据结构return response.data,到达方法响应拦截器中的数据类型已经不再是AxiosResponse,导致在响应成功的拦截器中类型错误无法赋值,以及.then返回的类型不正确无法返回,正确应该为DTO类型,解决方案:

// 修改 AxiosRequestConfig 类型注解,默认类型为原来的 AxiosResponse,传递到响应成功的拦截中进行泛型注解,用于在方法中重新修改返回的数据类型
// 最终的泛型类型注解到达 responseSuccessInterceptor 中
interface interceptorHooks<T> {beforeRequestInterceptor: (config: AxiosRequestConfig) => AxiosRequestConfigrequestErrorInterceptor: (error: any) => anyresponseSuccessInterceptor: (result: T) => TresponseFailInterceptor: (error: any) => any
}
export type IinterceptorHooks<T = AxiosResponse> = Partial<interceptorHooks<T>>

export interface IHttpRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {interceptors?: IinterceptorHooks<T>
}

// 修改 Http class request方法,通过 泛型 T 接受方法传达过来的 DTO 类型注解,传递到 IHttpRequestConfig 中修改 responseSuccessInterceptor 的参数类型注解以及返回值注解,在单个方法独享自定义拦截器中就可以接受参数,并且返回正确的 DTO 类型数据
// 由于在下面的 this.axios.request() 方法中,返回的数据已经被上一个拦截器扒皮修改了,返回的 result 类型注解中存在类型不正确情况(正确返回应返回response.data的类型注解),实际返回为 AxiosResponse<any,any>类型注解,导致数据返回到最外层方法时编辑器报错(最外层接受 DTO 类型),所以需要手动修改this.axios.request() 方法中返回的类型注解 this.axios.request<any, T>()
request<T>(config: IHttpRequestConfig<T>): Promise<T> {return this.axios.request<any, T>(config).then((result) => {// 存在单个方法独享自定义拦截器Hooks(响应成功)if (config.interceptors?.responseSuccessInterceptor) {// 立即执行beforeRequestInterceptor方法,传入config处理返回新的处理后的configresult = config.interceptors.responseSuccessInterceptor(result)}return result}).catch((error) => {// ...})} 

拦截器执行顺序

由于在 constructor(构造函数) 代码顺序:单个实例独享拦截器 -> 所有实例共享拦截器 ->…

🌄单个实例独享拦截器位于所有实例共享拦截器之前,执行顺序为:

所有实例共享请求拦截器 -> 单个实例独享请求拦截器 -> 单个实例独享响应拦截器 -> 所有实例共享响应拦截器

反之,则:

🌅所有实例共享拦截器位于之前单个实例独享拦截器,执行顺序为:

单个实例独享请求拦截器 -> 所有实例共享请求拦截器 -> 所有实例共享响应拦截器 -> 单个实例独享响应拦截器

🌏单个方法独享拦截器(单个实例独享拦截器位于所有实例共享拦截器之前) ,执行顺序为:

单个方法请求拦截器 -> 实例共享请求拦截器 -> 单个独享请求拦截器 -> 单个独享响应拦截器 -> 实例共享响应拦截器 -> 单个方法响应拦截器

请求拦截:constructor先执行的代码(use()),拦截器里面的回调hook后被执行,反之,先被执行

响应拦截:constructor先执行的代码(use()),拦截器里面的回调hook先被执行,反之,后被执行

需要修改执行顺序可调整代码的执行顺序

操作场景控制

由于存在三种拦截器,存在一些复杂的操作场景时,比如,通过给所有实例或者单独实例提前添加了操作loading、错误捕获...,但是现在需要在某个请求方法中不进行此操作时,如何解决?

解决方案:通过继续扩展特定的 IHttpRequestConfig 类型属性,因为单个方法请求拦截器是最先执行的,IHttpRequestConfig 配置项,在所有的拦截器中是共享的,层级传递的,在拦截器中判断特定的属性值关闭不需要的操作

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言习题练习8--二进制操作符

IO型--从main函数开始写&#xff0c;要写输入、计算、输出 接口型--不需要写主函数&#xff0c;默认主函数是存在的&#xff0c;你只需要完成函数就行 一、二进制中1的个数 (12条消息) C语言丨关键字signed和unsigned 的使用与区别详解_Emily-C的博客-CSDN博客_signed unsi…

【笔记】samba shell 脚本 离线安装 - Ubuntu 20.04

前言 按照官网调试代码、网上各种步骤来走&#xff08;还收费&#xff09;都不行 结果发现是防火墙问题 公司服务器安装的ufw使用失效&#xff0c;导致端口号放行添加失败 换用firewall-cmd成功 现在免费放下代码&#xff0c;气死他们收费的 目录 ├── home│ ├── k…

linux备份mysql8.0数据库脚本

文章目录环境要求步骤1、创建一个.sh文件编写shell脚本2、添加定时任务环境要求 linux系统&#xff0c;安装了mysql8.0 步骤 1、创建一个.sh文件编写shell脚本 创建文件的命令&#xff1a; vim ***.shshell文件文件参考自文章 链接 export LANGen_US.UTF-8 #注意&#xf…

测试开发技术:Python测试框架Pytest的基础入门

Pytest简介 Pytest is a mature full-featured Python testing tool that helps you write better programs.The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries. 通过官方网站介绍…

十五、Lua 协同程序(coroutine)的学习

Lua 协同程序(coroutine) 什么是协同(coroutine)&#xff1f; Lua 协同程序(coroutine)与线程比较类似&#xff1a;拥有独立的堆栈&#xff0c;独立的局部变量&#xff0c;独立的指令指针&#xff0c;同时又与其它协同程序共享全局变量和其它大部分东西。 协同是非常强大的功…

2646-61-9, 脯氨酰内肽酶(PEP)底物: Z-GPLGP-OH

编号: 160473中文名称: 脯氨酰内肽酶&#xff08;PEP&#xff09;底物&#xff1a;Z-Gly-Pro-Leu-Gly-ProCAS号: 2646-61-9单字母: Z-GPLGP-OH三字母: Cbz-Gly-Pro-Leu-Gly-Pro-COOH氨基酸个数: 5分子式: C28H39O8N5平均分子量: 573.64精确分子量: 573.28等电点(PI): -pH7.0时的…

Arduino程序设计(三) 光照采集 + 温度采集

光照采集 温度采集前言一、光敏电阻检测环境光二、DS18B20检测环境温度总结参考文献前言 本文主要介绍两种常见的传感器采集环境参数&#xff0c;即光照传感器和温度传感器。光照传感器采用光敏电阻GL3516&#xff08;5-10K&#xff09;检测环境光。温度传感器采用DS18B20检测…

2022Q3家电行业高增长细分市场分析(含热门品类数据)

2022年&#xff0c;在大环境的影响下&#xff0c;大众消费偏好更趋于理性化、追求高性价比&#xff0c;不少行业增速有所放缓&#xff0c;在此背景下&#xff0c;2022年Q3季度中&#xff0c;消费市场中仍有一些高增长概念涌现。 在家电行业中&#xff0c;我们发现了3个高增长品…

【重识云原生】第六章容器基础6.4.9.5节——端点切片(Endpoint Slices)

1 EndpointSlice特性 Kubernetes v1.21 [stable] 端点切片&#xff08;EndpointSlices&#xff09; 是一个新 API&#xff0c;它提供了 Endpoint API 可伸缩和可拓展的替代方案。EndpointSlice 会跟踪 Service Pod 的 IP 地址、端口、readiness 和拓扑信息。 在 Kubernetes v…

一文看懂页面置换算法

页面置换算法分为两类 1、局部页面置换算法 最优页面置换算法&#xff08;OPT、optimal&#xff09;先进先出算法&#xff08;FIFO&#xff09;最近最久未使用算法&#xff08;LRU,Least Recently Used&#xff09;时钟页面置换算法&#xff08;Clock&#xff09;最不常用算法…

【算法 | 实验18】在字符矩阵中查找给定字符串的所有匹配项

文章目录题目描述思路分析bug记录&#xff1a;"error: >> should be > > within a nested template argument list"代码题目描述 题目 在字符矩阵中查找给定字符串的所有匹配项 给定一个MN字符矩阵&#xff0c;以及一个字符串S&#xff0c;找到在矩阵中所…

给Git仓库添加.gitignore:清理、删除、排除被Git误添加的临时文件

文章目录一、前言二、发现提交的临时文件三、去掉临时文件的方法3.1 添加.gitignore3.2 删除临时文件缓存3.3 添加后的效果一、前言 最近维护代码过程中&#xff0c;发现某APP代码库里被提交了许多临时文件&#xff0c;而这些临时文件每次都会变化&#xff0c;所以导致每次修改…

为什么要写单测

一、什么是单元测试 “在计算机编程中&#xff0c;单元测试又称为模块测试&#xff0c;是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中&#xff0c;一个单元就是单个程序、函数、过程等&#xff1b;对于面向对象编程&#xff0c;最…

大屏经典组件:“无限滚动” 从分析到开发

&#x1f4d6;阅读本文&#xff0c;你将 理解大屏 “无限滚动组件” 的开发思路跟随作者&#xff0c;一步步完成一个高性能 “无限滚动组件” 的开发收获一份该实现的粗糙源码。 一、无限滚动&#xff1a;事件/告警 的有力帮手 1.1 为什么需要滚动列表 大屏之所以 “炫酷” …

稳压二极管稳压电路如何设计

在一些电流不大的地方&#xff0c;一般毫安级别&#xff0c;有时候我们可以利用稳压二极管去设计一个我们需要的电压。 大家可以看下稳压二极管的伏安曲线 在反向电压下&#xff0c;尽管电流在很大的范围内变化&#xff0c;而稳压二极管两端的电压却基本上稳定在击穿电压附近&a…

[附源码]java毕业设计旅游产品销售管理

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

CISCN 2022 初赛 web 复现

[CISCN 2022 初赛]ezpop 可以看到版本&#xff0c;那么直接上网找链子打 www.zip 查看路由&#xff0c;是 Index/test&#xff0c;然后 post 传参 a <?php // 保证命名空间的一致 namespace think {// Model需要是抽象类abstract class Model {// 需要用到的关键字priv…

【vue】vuex中modules的基本用法

1&#xff0c;什么时候用modules 由于使用单一状态树&#xff0c;应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时&#xff0c;store 对象就有可能变得相当臃肿。 为了解决以上问题&#xff0c;Vuex 允许我们将 store 分割成模块&#xff08;module&#xff09…

NFT交易平台开发 创建NFT数字藏品平台

为什么需要 NFT 市场&#xff1f; NFT Marketplace 允许用户购买、出售、交易、查看或创建自己的 NFT&#xff0c;就像他们需要一个市场来购买物理或数字世界中的大多数产品一样。几乎每个人都可以进入 NFT 市场&#xff0c;但要做到这一点&#xff0c;用户必须满足以下要求&a…

第3关:节点状态检查、数据查看和更新

首先&#xff0c;需要启动服务器&#xff0c;并使用zkCli.sh连接服务器&#xff0c;进入客户端命令行界面&#xff08;如第一关所述&#xff09;。 节点状态包含以下信息&#xff1a; czxid: 节点创建时的时间戳。mzxid: 节点最新一次更新发生时的时间。ctime&#xff1a; 节…