封装一个uniapp网络模块

2,364 阅读8分钟

前言

虽然最终成果是封装一个uniapp的网络网络模块,但思路是不同开发语言都相通的,自觉还是有点参考价值的

项目地址:uniapp-frame: uniapp开发框架 (gitee.com)

一段网络请求可以分为三个步骤

  1. 请求前
    1. 主要工作如下:
    2. 设置接口请求地址
    3. 设置参数
    4. 请求头
    5. 请求方法
  2. 请求中
    1. 客户端与服务端建立连接,发送数据。这一步骤uniapp已经替我们做了,开发者只需要调用uni.request
  3. 请求后
    1. 主要工作如下:
    2. 设置请求成功,错误回调
    3. 数据转换,网络错误处理
    4. 回调到UI

可以优化和封装的是请求前,请求后。 请求中 的工作uniapp平台替我们做好了。一个完整的网络请求流程如下。

插入语:封装不是拍脑瓜子,想一出是一出,一定是有原因,有根据的。文本网络请求封装的根据是官方提供的uni.request() 。根据日常使用经验,总结哪里不方便,重复代码多,运用累计的编程知识进行封装 (经验不足的程序员是干不好封装代码这个活的)

uni.request({
		url:"https://www.wanandroid.com/article/list/0/json",
		method:"GET",
		header:{},
		data:{
			"page_size ":10,
		},
		success: (response) => {
			//todo 解析数据
			let statusCode = response.statusCode;
			//判断网络状态码
			if (statusCode === 200) {
				let httpData = response.data;
				//接口业务码
				if (httpData.errorCode === 0) {
					let data = httpData.data;
					console.debug("=======================success============================");
					console.debug(data);
				} else {
					//接口业务码错误
					let code =  httpData.errorCode;
					let message = httpData.errorMsg;
				}
			} else {
				//网络状态码错误
				let code =  statusCode;
				let message = response.errMsg;
			}
		},fail: (error) => {
			//todo 错误处理
			console.debug("=========================fail==========================");
			// {"errMsg":"request:fail"}
			let code =  -1;
			let message = error.errMsg;
		}
	})

根据划分请求前,中,后的思路 分析上述代码 可以按照下图方式划分。

Untitled.png

优化思路分析

请求中 客户端与服务端交互 开发人员是无法控制的。咱不用管

请求前存在如下可优化的点

  1. url,原始调用方式 传递完整的接口路径
    1. www.wanandroid.com/article/lis…
    2. 在实际开发中,接口服务器最少会分为两个环境,开发环境和生产环境。还有可能连接同事的电脑进行联调测试。
    3. 相同点是接口资源地址article/list/0/json 不同点是服务器地址www.wanandroid.com/
    4. 所以在各种组件,封装中会出现baseUrl
  2. method
    1. 确定这次请求的请求方式,常见的就两种get,post,
    2. post请求 根据设置的请求头content-type的内容不同,有三种提交数据的方式
      1. application/json。 表明 requestBody中的数据是 json字符串
      2. application/x-www-form-urlencoded。表明 requestBody中的数据是 key,value键值对
      3. multipart/form-data。表明 有文件需要上传
    3. 仔细想想前端的网络基本上也就这四种情况。所以可抽象出四种方法供外界调用。
      1. get
      2. postJson
      3. postKV
      4. upload
  3. header 和 data 。
    1. 一般来说用来传递业务参数,有些接口有参数,有些接口没有参数,没什么共性,看起来不需要封装
    2. 但是真实项目中,肯定会有服务端参数校验,会传递公共参数。比如:用户token,平台,版本号,加密参数,时间戳巴拉巴拉的。 这种情况下,就需要统一处理,不可能前端写一个业务接口 就复制公共参数的逻辑。

请求后优化逻辑梳理

  1. 请求后做了一件事,就是解析数据。从成功和失败两方面解析数据
  2. 从代码注释可以看出,需要判断网络状态,接口业务码全部成功之后 才能够取出数据
  3. 如果有一个位置出错了,则进入错误逻辑
    1. 在错误逻辑中,声明了两个字段
      1. code,错误码
      2. message,错误信息
    2. 大多数情况UI 只会使用message 进行错误提示,弹个toast。 在特殊情况,当前接口逻辑复杂,当发生了某种错误时,前端需要进行逻辑处理时,code就起作用了。
    3. 最典型的用例是 用户token失效,接口返回错误码9527,前端添加判断,如果code === 9527 则清空用户信息,跳转到登录页面。
    4. 定义code 和 message 还有一个好处是 结构清晰。如果网络请求出错误,把错误信息传递到UI层时。UI层接收的数据结构始终是一致的:code,message
  4. 在未封装的情况下,一次网络请求有4处于UI的数据交互。一处成功三处错误。UI进行错误处理很麻烦,有很多重复代码
  5. 小总结,在数据解析的过程中,大部分代码都是模板代码不会变的。我们只需要提供成功和失败的回调方法,把解析后的数据传递到UI层,让UI层能够拿来就用。

封装目的

  1. 代码分层
    1. 把网络请求 和 处理的数据的逻辑 移入另一个js文件 保证vue文件的尽量干净
  2. 添加公共参数
    1. 在拦截器中处理
  3. 优化回调
    1. 使用Promise 或者Callback回调函数处理网络请求回调,
  4. 设置baseUrl,支持多域名
    1. 请求时不写baseUrl,填写相对路径 但是要添加一个前缀(key) 比如:api/article/list/0/json
    2. api就是前缀,当作key。通过key找到对应的value,即baseUrl
    3. 在拦截器中 取出url,解析成两部分 前缀(api) 和 真正的接口地址(/article/list/0/json)
    4. 代码 保存一个map。key:前缀,value:baseUrl 或者 用 if 判断也可以。 能找到对应的baseUrl就行
  5. 请求后处理数据转换,网络错误
    1. 在拦截器中拿到返回结果,统一处理
  6. 日志输出
    1. 在拦截器拿到request 和 response 控制台输出即可
  7. 添加公共参数
    1. 拦截器 拿到request中header,根据业务赋值即可

阶段问题总结

短暂的总结一下目前的核心思路有两点

  1. 使用Promise 简化网络请求的使用
  2. 利用官方提供的拦截器完成多域名,数据转化,网络错误处理,日志输出,添加公共参数

多域名,日志输出,公共参数根据上述思路可以完成。

但是做接口数据转换和错误处理的时候遇到了麻烦如下:

  1. 根据文档 uni.request 请求成功返回 response 只有:data,statusCode,header,cookies
  2. 不同的服务器返回的数据结构可能不一样,所以就必须进行业务区分
  3. uni.request 返回的response 没有数据返回可以帮助我们做业务判断 那么就得想别的办法
  4. 还有一点官方拦截器 无法修改返回的数据结构
  5. 所以官方拦截器的思路就被pass了

现在的问题是如何处理多域名情况下的数据转化和错误处理。这块的逻辑肯定是不能节省的,要为不同的域名编写不同的逻辑。

新的封装思路

在上述拦截器的封装思路中,其实陷入一点点误区,如下:

  1. 拦截器一统天下,企图使用拦截器处理所有的业务情况。把拦截器的优先级抬的过高。
  2. 拦截器是帮助开发人员处理网络请求公共业务的。但正确的思路应该是先有 服务端 后有拦截器
    1. 用面向对象的思路来说,前端先创建一个 服务端对象(HttpClient 网络请求客户端),而拦截器是服务端对象的一个属性
  3. 因为每个服务端的逻辑都不一样,前端不应该预先设定有拦截器,而是否要定义拦截器,是根据服务端逻辑确定。如下图所示

Untitled 1.png 理清楚拦截器与服务端的关系之后,突然有种茅塞顿开的感觉。虽然使用的是js,但用面向对象的思路去封装是目前能够想到的最好的办法。如下图

Untitled 2.png 封装的思路上图应该表达的很清楚,一个域名(服务端)对应一个前端的 XXXService。每个XXXService可以维护自己的业务逻辑,对外只开get,post方法。

这样的封装方式,可拓展性很强,随便添加逻辑,XXXService对象之间互不影响。对于调用端来说,只需要调用XXXService的get,post方法。

emm 感觉挺完美 哈哈哈哈

拦截器讨论

这个封装中没有拦截器的概念,想加随时可以加,但感觉没有必要。在web端axios,Android端okHttp这些知名的网络请求库都提供了拦截器功能。在日常工作中,对于拦截器的使用大多用来添加公共参数 和 处理返回的数据。

现有的封装完全能够满足日常需求,而且可扩展性很强。好的代码不是使用多牛逼的技术和封装思路,尽量简单的情况下能够完美解决问题就是好的代码

所以没有引入拦截器的概念

参考知识

  1. 完全弄懂JavaScript模块化(导出和导入) - 知乎 (zhihu.com)
  2. 拦截器 - uni-app官网 (dcloud.io)
  3. JavaScript 教程 - 网道 (wangdoc.com)