这套题还不错,感兴趣的猿可以试一试:前端开发工程师
项目目录
首先是我的项目架构,由于我的项目分的比较细,我只展示部分和本文章相关的模块。
|-----src
| |-----common 公共文件
| | |-----Api 后端接口
| | | |-----modules 接口模块
| | | | |-----User.js 用户接口模块
| | | |-----index.js 公共接口模块
| | |-----Helper 公共Helper
| | | |-----index.js 公共Helper
| | | |-----.....js 其他公共方法抽离
| |-----sevice 网络请求封装(axios封装)
| | |-----api.js 请求方式封装
| | |-----request.js axios封装
| |-----utils 工具类
| | |-----vue-install.js vue全局绑定工具类
| |-----main.js webpack入口文件
Api后端接口
为什么要把接口单独抽离出模块呢?是因为在中大型项目中后端可能使用saas平台架构,拆分出不同模块,为了方便维护,快速查找定位接口,我们就需要将单文件进行解耦,抽离相同模块方便维护。当然如果后端没有用saas平台架构或接口较少的话,可以不使用这种方式直接写到index.js中这些都是根据个人需求去改变的无需纠结。
// index.js
import User from './modules/User'
export default {
User, // 将独立的模块抽离到单独的js文件中可以方便统一维护
// 将公共的接口抽离出来方便维护 method:请求方式 url:接口地址
sendSms: { method: 'post', url: '' },
uploadFile: { method: 'post', url: '' }
}
// User.js
export default {
getUser: {method: 'get', url: ''}
}
公共Helper
同理,helper也可以抽离出不同的方法,方便维护。
// index.js
import deepClone from './deepClone'
import FormatDate from './FormatDate'
const Helper = {
/**
* 深度拷贝
* @param {object} 对象
* @return {object}
*/
deepClone,
/**
* 日期格式化
* @param {date} timestamp 日期/时间戳
* @param {string} format 格式 默认值 Y-m-d
* @return {string}
*/
FormatDate,
/**
* 获取指定日期之间的所有日期
* 日期格式 yyyy-MM-dd
* @param {string} start 开始日期
* @param {string} start 结束日期
* @return {array}
*/
getAllDate,
/**
* 随机字符串
* @param {boolean} lower 小写字母
* @param {boolean} upper 大写字母
* @param {boolean} number 数字
* @param {boolean} symbol 特殊字符
* @param {boolean} length 长度
*/
RandomString,
/**
* 计算请求分页参数
* @param {object} route 当前页面路由
* @return { limit: 10, page: 1, offset: 0 } limit = 长度, page = 页码, offset = 偏移量
*/
getPerPage (query) {
let limit = parseInt(query.limit) || 10,
page = parseInt(query.page) || 1,
offset = (page - 1) * limit;
return {
limit, page, offset
}
},
/**
* 清理对象
* 去除属性值为 空(字符串), null, undefined
* 转换值为数字,true,false的字符串为对应的数据类型
* @param {object} obj 对象
* @return {object}
*/
clearObject (obj) {
let o = {};
for (const k in obj) {
let v = obj[k];
if (v === null || v === undefined) continue;
// 非字符串
if (toString.call(v) !== '[object String]') {
o[k] = v;
continue;
}
v = obj[k].trim();
// 过滤空值
if (v.length === 0) continue;
// 正数,负数,浮点数
if (/^(-?\d+)(\.\d+)?$/.test(v)) {
o[k] = Number(v);
}
// 布尔值
else if (v === 'true' || v === 'false') {
o[k] = (v === 'true');
}
// false
else {
o[k] = v;
}
}
return o;
}
}
export default Helper;
由于有些代码比较长我这里只分享一部分模块。
// 深度复制
const deepClone = (obj) => {
let o;
if (typeof obj === 'object') {
if (obj === null) {
o = null
} else {
// 数组
if (obj instanceof Array) {
o = [];
for (const item of obj) {
o.push(deepClone(item))
}
}
// 对象
else {
o = {};
for (const j in obj) {
o[j] = deepClone(obj[j])
}
}
}
}
else {
o = obj;
}
return o;
}
export default deepClone;
到此我的common公共文件就完成了,其实中心思想还是将之前的单文件抽离出不同的模块,这样方便后面扩展。接下来是网络请求的封装。这里可能比较绕,不过仔细看过后还是能理解的。
sevice 网络请求封装(axios封装)
// api.js
import Api from '@/common/Api'
import Helper from '@/common/Helper'
import axios from './request'
const axiosApi = {
// get请求
createGet (url) {
return (args = {}) => {
return axios({
method: 'GET',
url: url,
...args
})
}
},
// post请求
createPost (url) {
return (args = {}) => {
let parmas = ''
/*
不挂载到url地址上 为什么我这里要这么写呢,
因为后端post接收参数有一部分是从地址栏获取,
虽然我很诧异但是我直接给他两份他随便获取,
当然可以根据notMountUrl参数选择不绑定到地址栏
*/
if (!args.notMountUrl) {
parmas = `?${Helper.formatParams(args.data)}`;
}
return axios({
method: 'POST',
url: `${url}${parmas}`,
...args
})
}
},
// .... 当然你也可以写更多请求方式或者根据不同的需求调用不同的Axios封装
// 创建API请求方法
crateApi (apiConfig) {
let methods = {}
// 获取所有api的Key值进行循环
Object.keys(apiConfig).forEach(key => {
let item = apiConfig[key]
// 子集请求 判断是否是单独模块 如果是就递归子集
if (!item.method && !item.url) {
return methods[key] = this.crateApi(item)
}
// 接口动态创建
const method = item.method.toLocaleUpperCase()
if (method === 'GET') {
methods[key] = this.createGet(item.url)
}
else if (method === 'POST') {
methods[key] = this.createPost(item.url)
}
})
return methods
}
}
// 这里一定是抛出创建方法,如果你晕了可以从这一步往回走,慢慢你就懂了
export default axiosApi.crateApi(Api)
// request.js
import axios from 'axios'
// 这里我使用的是vant ui库可根据需求更换不同的ui库
import { Toast } from 'vant';
import store from '@/store'
import router from '@/router'
// statusCode 错误状态码 这里错误码其实就是将网络请求中的所有status进行了键值对的封装方便调用
import statusCode from '@/common/BaseData/status_code.js'
// 是否为生产环境
const isProduction = process.env.NODE_ENV == "production";
// 创建一个axios实例
const service = axios.create({
baseURL: !isProduction ? '/api' : process.env.VUE_APP_BASE_URL, // 前缀由于我使用的是webpack的全局变量,这里其实也可以写死
withCredentials: true, // 当跨域请求时发送cookie
timeout: 20000 // 请求超时时间
})
// 请求拦截器
service.interceptors.request.use(
config => {
// 获取token
const hasToken = store.getters.Token
// 设置 token设置token
if (hasToken) {
config.headers['token'] = hasToken
}
// .... 其他操作根据具体需求增加
return config
},
error => {
// 处理请求错误
// console.log(error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
/**
* 通过自定义代码确定请求状态
*/
response => {
const res = response.data
// 这里的error是后台返回给我的固定数据格式 可根据后端返回数据自行修改
const { error } = res;
// error不为null
if (error) {
// 弹出报错信息
Toast.fail({
icon: 'failure',
message: error.msg
})
// 后端返回code的报错处理
switch (error.code) {
case '1000':
case '1001':
case '1002':
case '1003':
router.replace({ path: '/error', query: { code: error.code, msg: error.msg } })
break;
case '6000':
case '6100':
// 清空Token 重新登录
store.dispatch('user/resetToken')
return Promise.reject(new Error(error.msg));
case '6200':
case '7000':
case '19000':
default:
// 如果状态码不是 则判断为报错信息
return Promise.reject(new Error(error.msg))
}
} else {
// 正常返回
return res
}
},
error => {
// 这里就是status网络请求的报错处理 主要处理300+ 400+ 500+的状态
console.error('err:' + error)
// 弹出请求报错信息
Toast(statusCode[error.statusCode])
return Promise.reject(error)
}
)
// 向外抛出
export default service
service网络请求封装结束,这里主要的是api.js的封装比较绕,其实就是动态创建后端接口的方法,万变不离其宗,都是换汤不换药的写法
utils工具类
最后的工具类其实就是将我们所有的方法绑定到vue的prototype原型上,以此形成完整的闭环 如果不了解vue绑定插件的机制的话,可以去vue.js官网自行查找,其实没多难。
// vue-install.js 全局绑定
import Api from '@/service/api'
import Helper from '@/common/Helper'
export default {
install: (Vue) => {
// 全局注入 $api 请求 将我们api.js抛出的方法绑定到vue实例上
Vue.prototype.$api = Api
// 全局注入 $helper 辅助方法 同理将helper公共方法绑定到vue上
Vue.prototype.$helper = Helper
}
}
工具类的绑定结束,致辞我们就差最后一步就完成我们的闭环了,那就是引入到main.js中
main.js webpack入口文件
import Vue from 'vue'
import router from './router'
import store from './store'
// .... 其他依赖引入
import vueInstall from './utils/vue-install' // 全局注册
// 全局绑定 直接使用use方法绑定
Vue.use(vueInstall);
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
使用方法
// 某页面
async created () {
// 请求User模块
const res = await this.$api.User.getUser({
data: {
// 参数.....
},
// 选择是否挂载到url上
notMountUrl: false
})
// 深拷贝
const newResult = this.$helper.deepClone(this.result)
}
大功告成,这就是目前我项目中网络请求与helper公共方法的封装。希望对大家有所帮助,同时也虚心接受大家的各种建议和意见。希望大家在评论区进行评论。我会持续对我所掌握的知识进行分享。不忘初心坚持开源开放的学习态度。