TypeScript封装Axios
TypeScript封装Axios
Axios的基本使用
因axios基础使用十分简单,可参考axios官方文档,这里不在介绍他基本用法,主要讲解拦截器。
拦截器主要分为两种,请求拦截器和响应拦截器。
请求拦截器:请求发送之前进行拦截,应用于我们在请求发送前需要对请求数据做一些处理。例如:
- 携带token
- 当请求时间过长时,设置loading
响应拦截器:在响应到达时进行拦截,应用于在我们业务代码中拿到数据之前,需要对数据做一定处理。例如:
- 转换数据格式
- 移除loading
为什么要封装Axios
在项目中会有很多的模块都需要发送网络请求,常见的比如登录模块,首页模块等,如果我们项目中直接使用诸如axios.get(), axios.post(),会存在很多弊端,哪些弊端呢?
- 首先这样做会导致我们每个模块对axios依赖性太强,意味着我们项目中的每个模块都和一个第三方库耦合度较高,这样的话,如果axios不在维护,我们要更换库的时候将非常麻烦,我们可以假设一下,随着时间的推移,axios可能因为浏览器的升级,Webpack的改变而出现一些bug, 然而axios已不再维护,这时我们往往需要切换库,这就意味着我们需要去修改每个模块中的请求相关的代码,显而易见,非常繁琐。
- 还有一点,在我们发送网络请求的时候,往往会有很多共同的特性,比如说,在我们成功登录之后的其他请求中,我们往往需要在请求头中添加token,然后发送请求;在每次请求中,我们想展示一个loading… 这些功能如果在每次请求的逻辑中都写一遍,很明显,我们的代码重复度太高了。
而axios封装之后,则会带来很多好处:
解决以上弊端,降低与第三方库的耦合度,这样我们将来需要更换库时,只需要修改我们封装后的request即可,这样我们往往只是修改封装后一两个文件,而不再需要每个模块每个模块的修改。
在我们开发中,我认为class的相关语法封装性会更好,因此这里我选择尝试用类相关的概念来封装axios。我想要的封装后达到的效果:可以直接在其他项目使用。
利用面向对象的思想对Axios进行封装
基础封装
封装一个Request的类,使得在外部可以调用此类的构造函数创建实例,创建的实例就对应axios实例,http/request.ts
中代码如下:
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";// 创建这个类的目的:每个创建出的HDRequest的实例都对应一个axios实例
class Request {// 创建实例的方法:constructor()构造实例instance: AxiosInstance;constructor(config: AxiosRequestConfig) {this.instance = axios.create(config);}// 二次封装网络请求的方法request(config: AxiosRequestConfig) {return this.instance.request(config);}
}
// 暴露Request类
export default Request;
基本配置信息单独写在一个文件中,config/index.ts
中代码如下:
const CONFIG = {// 服务器地址serverAddress: 'https://91huajian.cn',// 其他基础配置项,入最长响应时间等
};
export default CONFIG;
在http/index.ts
中创建一个Request
类的一个实例http
,并配置这个实例:
import Request from "./index";
import CONFIG from "@/config";// 创建一个axios实例
const http = new Request({baseURL: CONFIG.serverAddress,timeout: CONFIG.maxTimeout,
})export default http;
在接口中使用该实例发送请求:
// 在http/api/sponsor.ts文件中封装发送请求的方法,在页面组件任意位置随意调用
import http from '../request';
// 查询赞助
export const getSponsorListAsync: any = (params: any) => { return http.request({url: '/huajian/common/getSponsorList',method: 'get',params: params});
}
拦截器的类型
拦截器分为三种:
- 类拦截器(在封装的axios类(文中类为
Request
类)上定义的拦截器)- 实例拦截器(在利用
Request
类实例化对象时传递的参数中定义的拦截器)- 接口拦截器(在调用实例时传入的参数中定义的参数)
配置全局拦截器(类拦截)
保证每一个axios实例对象都有拦截器,即本使用Request实例化的对象发送的请求都会被配置的拦截器所拦截并执行拦截器中的程序。
类拦截器比较容易实现,只需要在类中对axios.create()
创建的实例调用interceptors
下的两个拦截器即可,实例代码如下:
import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios';
import { RequestConfig } from './types/types';
class Request {instance: AxiosInstance;constructor(config: RequestConfig) { this.instance = axios.create(config);// 添加全局请求拦截器,每个实例都有this.instance.interceptors.request.use(// 拦截到请求中携带的所有配置项config(config: AxiosRequestConfig) => {console.log('全局请求拦截器', config);return config;},(err: any) => err)// 添加全局响应拦截器,每个实例都有this.instance.interceptors.response.use(// 拦截到服务器返回的响应体res(res: AxiosResponse) => {console.log('全局响应拦截器', res);return res.data;},(err: any) => err;}request(config: AxiosRequestConfig) { return this.instance.request(config);}
}
export default Request;
我们在这里对响应拦截器做了一个简单的处理,就是将请求结果中的.data
进行返回,因为我们对接口请求的数据主要是存在在.data
中,跟data
同级的属性我们基本是不需要的。
为某一Request实例单独配置拦截器(实例拦截)
实例拦截器是为了保证封装的灵活性,因为每一个实例中的拦截后处理的操作可能是不一样的,所以在定义实例时,允许我们传入拦截器。
新创建一个实例http2
在它的config
中传入拦截器属性,但是axios
的AxiosRequestConfig
类型中并没有拦截器属性类型。
因此需要对types/index.ts
中的构造函数中的config
类型进行扩展(extends)。首先我们定义一下interface,方便类型提示,代码如下:
import { AxiosRequestConfig, AxiosResponse } from "axios";
// 拦截器的类型
export interface RequestInterceptors<T> {// 请求拦截器requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig;// 在发送请求之前做些什么requestInterceptorCatch?: (error: any) => any;// 对请求错误做些什么// 响应拦截器responseInterceptor?: (res: T) => T;// 对响应数据做点什么responseInterceptorsCatch?: (err: any) => any;// 对响应错误做点什么
}
// 自定义传入的参数
export interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig{interceptors?: RequestInterceptors<T>;
}
然后再创建新的实例,并在实例中引入定义的拦截器:
import Request from "./index";
import CONFIG from "@/config";
import {RequestConfig} from "./types/types";
import { AxiosResponse } from "axios";// 创建一个axios实例
const http = new Request({baseURL: CONFIG.serverAddress,timeout: CONFIG.maxTimeout
})
const http2 = new Request({baseURL: CONFIG.serverAddress,timeout: CONFIG.maxTimeout,interceptors: {// 配置请求拦截器requestInterceptor: (config: RequestConfig) => { console.log('通过请求拦截器,拿到http2的请求配置参数',config);return config;},// 响应拦截器responseInterceptor: (result: AxiosResponse) => {console.log('通过响应拦截器,拿到http2的响应返回的结果',result);return result;}}
})export default {http,http2};
注意:这里的拦截器只能由使用http2
实例发送的请求才会执行。
我们的拦截器的执行顺序为实例请求→类请求→实例响应→类响应;这样我们就可以在实例拦截上做出一些不同的拦截,
此时在使用Request
实例化对象http2
时,我们传入的配置项中多了interceptors
配置项,那么在Request
类中我们就得接收并在实例化时执行:
import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios';
import { RequestConfig,RequestInterceptors } from './types/types';
class Request {instance: AxiosInstance;// 拦截器对象interceptorsObj?: RequestInterceptors<AxiosResponse>;constructor(config: RequestConfig) { this.instance = axios.create(config);this.interceptorsObj = config.interceptors;//接收实例对象传入的该实例的定制拦截器// 全局请求拦截器this.instance.interceptors.request.use((config: AxiosRequestConfig) => {console.log('全局请求成功拦截器', config);return config;},(err: any) => err) // 使用实例对象的自定义拦截器 针对特定的http2实例添加拦截器this.instance.interceptors.request.use(this.interceptorsObj?.requestInterceptor, // 请求前的拦截器this.interceptorsObj?.requestInterceptorCatch // 发送请求失败的拦截器)// 使用实例对象的自定义拦截器 针对特定的http2实例添加拦截器this.instance.interceptors.response.use(config.interceptors?.responseInterceptor,config.interceptors?.responseInterceptorsCatch);// 全局响应拦截器this.instance.interceptors.response.use((res: AxiosResponse) => {console.log('全局响应成功拦截器', res);return res.data;},(err: any) => {return err;});}request(config: AxiosRequestConfig) { return this.instance.request(config);}
}
export default Request;
同一个request实例的不同网络请求设置不同的拦截器(接口拦截)
现在我们对单一接口进行拦截操作,首先我们将AxiosRequestConfig
类型修改为RequestConfig
允许传递拦截器;然后我们在类拦截器中将接口请求的数据进行了返回,也就是说在request()
方法中得到的类型就不是AxiosResponse
类型了。
接口中同一个实例在发送不同的request
请求时一个配置了拦截器一个没配拦截器
import { http2 } from "..";http2.request({url: "/entire/list",params: {offset: 0,size: 20,},}).then((res) => {console.log(res);});http2.request({url: "/home/highscore",interceptors: {responseInterceptor: (config) => {console.log("来自接口定制的请求前的拦截");return config;},responseInterceptor: (res) => {console.log("来自接口的响应成功的拦截");return res;},},}).then((res) => {console.log(res);});
对request/index.ts
的request
方法进行进一步封装,使之能够立即执行传进来的拦截器:
// Request类的request方法:
// 二次封装网络请求的方法
request<T>(config: RequestConfig<T>): Promise<T> { return new Promise((resolve, reject) => {// 为同一个request实例的不同网络请求设置不同的拦截器// 不能将拦截器放在实例上,这样的话同一个实例的拦截器都是一样的了// 只能判断传进来的config中是否设置了拦截器,若设置了就直接执行// 执行this.instance.request(config)之前先执行requestInterceptor,并更新configif (config.interceptors?.requestInterceptor) {//立即调用拦截器函数执行config = config.interceptors.requestInterceptor(config);}// 由于执行完this.instance.request(config)之后才能对response结果进行拦截,是个异步的过程// 在Promise内部调用instance实例先执行this.instance.request(config),然后等待结果,之后以结果作为拦截器函数的参数进行调用this.instance.request<any, T>(config).then((res) => {// 如果给单个响应设置拦截器,这里使用单个响应的拦截器if (config.interceptors?.responseInterceptor) {res = config.interceptors.responseInterceptor(res);}resolve(res);}).catch((err: any) => {reject(err);})})
}
各种请求拦截的执行顺序:
拦截器执行顺序:接口请求 -> 实例请求 -> 全局请求 -> 实例响应 -> 全局响应 -> 接口响应
实例请求和全局请求的先后顺序取决于在Request
类constructor()
构造函数中两种请求的执行顺序。
取消请求
思路步骤:
- 创建一个数组用于存储控制器资源;
- 在请求拦截器中将控制器存入数组;
- 在响应拦截器中将控制器从数组中移除;
- 封装一个取消全部请求的方法;
- 封住一个可以取消指定请求的方法;
准备
我们需要将所有请求的取消方法保存到一个集合(这里我用的数组,也可以使用Map)中,然后根据具体需要去调用这个集合中的某个取消请求方法。
因此,我们首先进行类型定义:
// 一个取消请求对象,键位url,值为取消控制器
export interface CancelRequestSource { // 取消请求的标识[index: string]: AbortController;
}
然后我们在Request类中定义储存取消请求对象的数组,和存放请求url的数组
/*
存放取消控制对象的集合
* 在创建请求后将取消控制对象 push 到该集合中
* 封装一个方法,可以取消请求,传入 url: string|string[]
* 在请求之前判断同一URL是否存在,如果存在就取消请求
*/
cancelRequestSourceList ?: CancelRequestSource[];
/*
存放所有请求URL的集合
* 请求之前需要将url push到该集合中
* 请求完毕后将url从集合中删除
* 添加在发送请求之前完成,删除在响应之后删除
*/
requestUrlList ?: string[];
接着我们要准备两个方法,一个时根据url在取消控制对象数组中找到对应请求的方法,另一个时完成取消请求后删除存放url数组和存放取下请求对象数组中对象请求的方法。
//根据url找到取消请求对象数组中此次请求取消对象存放的地址
private getSourceIndex(url: string): number {return this.cancelRequestSourceList?.findIndex((item: CancelRequestSource) => {return Object.keys(item)[0] === url;}) as number;
}
//请求取消完成后,我们要删除对应请求和取消请求对象
private delUrl(url: string) {const urlIndex = this.requestUrlList?.findIndex((u) => u === url);const sourceIndex = this.getSourceIndex(url);// 删除url和AbortController对象urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1);sourceIndex !== -1 && this.cancelRequestSourceList?.splice(sourceIndex as number, 1);
}
在发送请求前存入AbortController
对象
const url = config.url;
// url存在 保存当前请求url 和 取消请求方法
if (url) {this.requestUrlList?.push(url);//将url存入url数组const controller = new AbortController();//构造实例化一个AbortController对象控制器config.signal = controller.signal//绑定请求this.cancelRequestSourceList?.push({[url]: controller//将该控制器添加入cancelRequestSourceList数组})
}
请求已经完成了删除保存的url和AbortController对象
this.instance.request<any, T>(config).then(res => {// 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器if (config.interceptors?.responseInterceptor) {res = config.interceptors.responseInterceptor<T>(res)}resolve(res)}).catch((err: any) => {reject(err)}).finally(() => {url && this.delUrl(url);// 请求执行完毕,删除保存在数组中的url和该请求的取消方法});
封装取消请求方法
- 封装取消全部请求
// 取消全部请求
cancelAllRequest() {this.cancelRequestSourceList?.forEach((source) => {const key = Object.keys(source)[0];source[key].abort();})
}
- 封装取消部分请求
// 取消请求
cancelRequest(url: string | string[]) {if (typeof url === 'string') {// 取消单个请求const sourceIndex = this.getSourceIndex(url);sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url].abort();} else {// 存在多个需要取消请求的地址url.forEach((u) => {const sourceIndex = this.getSourceIndex(u);sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u].abort();});}
}
相关文章:

TypeScript封装Axios
TypeScript封装Axios Axios的基本使用 因axios基础使用十分简单,可参考axios官方文档,这里不在介绍他基本用法,主要讲解拦截器。 拦截器主要分为两种,请求拦截器和响应拦截器。 请求拦截器:请求发送之前进行拦截&…...

指针(一)【C语言进阶版】
大家好,我是深鱼~ 【前言】: 指针的主题,在初阶指针章节已经接触过了,我们知道了指针的概念: 1.指针就是个变量,用来存放地址,地址的唯一标识一块内存空间(指针变量)&a…...

回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测(多指标,多图)
回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测(多指标,多图)效果一览基本介…...

springMVC 已解密的登录请求
问题描述: 解决方案: 1.对用户所输入的密码在页面进行MD5加密并反馈至密码输入框。 2. 手动生成SSL安全访问证书;在此不做介绍,相关方法可通过网上查找; 3. 将产品HTTP访问方式改为SSL安全访问方式;在Ap…...
机器学习赋能乳腺癌预测:如何使用贝叶斯分级进行精确诊断?
一、引言 乳腺癌是女性最常见的恶性肿瘤之一,也会发生在男性身上。每年全球有数百万人被诊断出乳腺癌,对患者的生活和健康造成了巨大的影响。早期的乳腺癌检测和准确的诊断对于提高治疗的成功率至关重要。然而,乳腺癌的早期诊断面临着许多挑战…...

Java后端开发面试题——框架篇
Spring框架中的bean是单例的吗?Spring框架中的单例bean是线程安全的吗? singleton : bean在每个Spring IOC容器中只有一个实例。 prototype:一个bean的定义可以有多个实例。 Spring bean并没有可变的状态(比如Service类和DAO类),…...

【新版】系统架构设计师 - 系统测试与维护
个人总结,仅供参考,欢迎加好友一起讨论 文章目录 架构 - 系统测试与维护考点摘要软件测试软件测试 - 测试类型软件测试 - 静态测试软件测试 - 动态测试软件测试 - 测试阶段软件测试 - 测试阶段 - 单元测试软件测试 - 测试阶段 - 集成测试软件测试 - 测试…...

使用 useEffect 和 reactStrictMode:优化 React 组件的性能和可靠性
使用useEffect和React.StrictMode是一种优化React组件性能和可靠性的常见做法。下面是关于如何使用这两个特性的示例: import React, { useEffect } from react;function MyComponent() {useEffect(() > {// 在组件挂载/更新时执行的副作用代码// 可以进行数据获…...

商业智能BI是什么都不明白,如何实现数字化?
2021年下半年中国商业智能软件市场规模为4.8亿美元,2021年度市场规模达到7.8亿美元,同比增长34.9%,呈现飞速增长的趋势。数字化时代,商业智能BI对于企业的落地应用有着巨大价值,逐渐成为了现代企业信息化、数字化转型中…...

【五子棋】
五子棋 文章目录 五子棋前言一、登录功能二.哈希表管理用户的会话和房间三.基于Websocket连接开发的功能1.匹配功能2.游戏房间3.挑战功能4.人机对战5.聊天功能 前言 这篇博客主要详细介绍我的五子棋项目的核心功能的实现细节,也就是详细介绍五子棋各个功能是如何实…...

docker 01(初识docker)
一、docker概念 Docker是一个开源的应用容器引擎;诞生于2013年初,基于Go 语言实现,dotCloud公司出品(后改名为Dockerlnc);Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux …...

爬虫逆向实战(十九)--某号站登录
一、数据接口分析 主页地址:某号站 1、抓包 通过抓包可以发现登录接口 2、判断是否有加密参数 请求参数是否加密? 通过查看“载荷”模块可以发现有一个jsondata_rsa的加密参数 请求头是否加密? 无响应是否加密? 无cookie是否…...

linux环境docker安装mysql
1:docker拉取mysql镜像(可有自己选择mysql版本) [rootlocalhost ~]# docker pull mysql:8.02: Docker 中启动 MySQL 容器,可以使用以下命令: docker run -d --name mysql_container -v ./sql:/docker-…...

taro h5 formData上传图片的坑-Required request part ‘file‘ is not present
描述:用formData上传图片 1、生成formData const formData new FormData() formData.append(file, data) // data是file formData.append(xxx, xxx) // 添加其他参数2、用taro.request请求 Taro.request({url: xxxx,data: formData,header: {Content-Type: mult…...

GNU GRUB version 2.06 Minimal Bash-lke line editing is supported 问题修复
一、问题背景 博主喜欢折腾系统,电脑原来有一个windows系统,想整一个Linux双系统,结果开机时出现以下画面: GNU GRUB version 2.06 Minimal Bash-lke line editing is supported. TAB lists possible comand completions, Anywh…...

Embedding 向量生成GPT数据使用相关
如果使用python3.6的版本,使用pycharm创建工程,那么默认会使用 docx包,这样运行程序会爆异常,突然想起以前请教的一个大神,想当 初,这个问题困扰了我 两天时间,在此记录一下: pytho…...

Jenkins工具系列 —— 配置邮箱 每个job下动态设置临时发送人
文章目录 安装插件添加邮箱认证邮箱申请(以QQ邮箱网页为例)jenkins添加邮箱认证 jenkins设置邮箱相关信息配置全局邮件单个JOB邮箱配置 安装插件 点击 左侧的 Manage Jenkins —> Plugins ——> 左侧的 Available plugins 添加邮箱认证 邮箱申请…...

华纳云:ubuntu中怎么查看进程占用的端口
在Ubuntu中,你可以使用以下命令来查看进程占用的端口: 打开终端(Terminal)。 使用 netstat 命令来查看进程占用的端口。以下是几个常用的命令示例: 查看所有进程占用的端口和地址: netstat -tuln 查看TCP端…...

【学会动态规划】 最长递增子序列(26)
目录 动态规划怎么学? 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后: 动态规划怎么学? 学习一个算法没有捷径,更何况是学习动态规划, 跟我…...

Azure虚拟网络对等互连
什么是Azure虚拟网络对等互联 Azure虚拟网络对等互联(Azure Virtual Network peering)是一种连接两个虚拟网络的方法,使得这两个虚拟网络能够在同一地理区域内进行通信。它通过私有IP地址在虚拟网络之间建立网络连接,不论是在同一…...

CTFhub-sql-整数注入
判断存在 sqli 注入 1 1 and 11 1 and 12 因为 11 为真,12 为假,且 11 与 1 显示的数据一样,那么就存在 sqli 注入 查询该数据表的字段数量 一、 2 3 1,2成功带出数据,3没有数据,所以有两个字段 二、 1 order by …...

管理类联考——逻辑——真题篇——按知识分类——汇总篇——二、论证逻辑——归纳——第三节 归纳论证有效性
文章目录 第三节 归纳论证有效性真题(2007-37)——归纳——归纳论证有效性——两面验证法真题(2000-60)——归纳——归纳论证有效性——构造对照组实验真题(2001-44)——归纳——归纳论证有效性——寻找针对该缺陷的选项第三节 归纳论证有效性 真题(2007-37)——归纳—…...

PaddleRS 1.0.0版本安装
PaddleRS 1.0.0版本安装 PaddleRS是百度飞桨、遥感科研院所及相关高校共同开发的基于飞桨的遥感影像智能解译开发套件, 支持图像分割、目标检测、场景分类、变化检测、图像复原等常见遥感任务。 PaddleRS致力于帮助遥感领域科研从业者快速完成算法的研发、验证和调…...

三维重建 PyQt Python MRP 四视图(横断面,冠状面,矢状面,3D)
本文实现了 Python MPR 的 四视图,横断面,冠状面,矢状面,3D MPR(multi-planner reformation)也称多平面重建,多重面重建是将扫描范围内所有的轴位图像叠加起来再对某些标线标定的重组线所指定的组织进行冠状、矢状位、…...

使用vscode编写插件-php语言
https://blog.csdn.net/qq_45701130/article/details/125206645 一、环境搭建 1、安装 Visual Studio Code 2、安装 Node.js 3、安装 Git 4、安装生产插件代码的工具:npm install -g yo generator-code 二、创建工程 yo code 选择项解释: 选择编写扩…...

【笔记】Spark3 AQE(Adaptive Query Execution)
提效 7 倍,Apache Spark 自适应查询优化在网易的深度实践及改进 Performance Tuning 配置Spark SQL开启Adaptive Execution特性 How To Use Spark Adaptive Query Execution (AQE) in Kyuubi 【spark系列3】spark 3.0.1 AQE(Adaptive Query Exection)分析 玩转Spark…...

【雷达】接收和去噪L波段雷达接收到的信号研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

【云原生】Docker Cgroups资源控制管理
目录 一、cgroups简介 cgroups有四大功能: 二、cpu时间片的概念 三、对CPU使用的限制 3.1 设置CPU使用率上限 (1)查看容器的默认CPU使用限制 (2)进行压力测试 (3)创建容器时设置CPU使用时…...

k8s部署prometheus
1、prometheus部署yml文件地址 github地址 2、下载yml文件 rootiZj6cd9joygowsf7am5hryZ:~# git clone https://github.com/redhatxl/k8s-prometheus-grafana.git Cloning into k8s-prometheus-grafana... remote: Enumerating objects: 21, done. remote: Total 21 (delta 0)…...

飞书小程序开发
1.tt.showModal后跳转页面 跳转路径要为绝对路径,相对路径跳转无响应。 2.手机息屏后将不再进入onload()生命周期,直接进入onshow()生命周期。 onLoad()在页面初始化的时候触发,一个页面只调用一次。 onShow()在切入前台时就会触发&#x…...