前言
传送地址:
设计模式已经连载到了第 3 期,由于下一期的观察者模式要配合团队的埋点性能博客一起上要暂缓一段时间,所以这一篇算彩蛋篇,推出一个开箱即用型的 fetch 项目实战。
封装 fetch 步骤
封装基础 fetch
未封装之前的 fecth 如下使用
fetch('https://www.baidu.com/search/error.html') // 返回一个Promise对象
.then((res) => {
return res.text() // res.text()是一个Promise对象
})
.then((res) => {
console.log(res) // res是最终的结果
})
如上是直接使用 fecth 的方法,但在项目中直接引用会有很多不便的地方,所以我们先简单封装一下,比如跨域配置、超时、各种请求等等的配置。
import qs from 'qs'
class Fetch {
constructor(config = {}) {
this.config = {
cache: 'no-cache', // * default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, same-origin, *omit
headers: {},
mode: 'cors', // no-cors, cors, *same-origin
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer', // *client, no-referrer
timeOut: 3000,
BASE_URL: '',
...config
}
}
send({ url, params, method = "GET", headers }) {
// 发送 ajax 请求
const { BASE_URL } = this.config
const ajax = new Promise((resolve) => {
fetch(BASE_URL ? `${BASE_URL}/${url}` : url, {
...this.config,
body: params,
headers,
method,
}).then((response) => {
return response.json()
}).then((data) => {
resolve(data)
})
})
// 设置超时时间
const time = new Promise((reject) => {
console.log(this.config.timeOut)
setTimeout(() => {
reject('time out')
}, this.config.timeOut);
})
return Promise.race([ajax, time])
}
// 封装请求
get({ url, query, headers }) {
return this.send({ url: `${url}?${qs.stringify(query)}`, headers, method: 'GET' })
}
post({ url, params, headers }) {
return this.send({ url, params, headers, method: 'POST' })
}
}
const newFetch = new Fetch();
newFetch.get({
url: 'https://api.github.com/users/octocat',
params: {
test: 1
}
}).then(data => {
console.log(data)
}).catch(err => {
console.log(err)
})
如上我们简单的封装了一个可以发送 get、 post 请求的 fetch 类,加入了超时跟网关,简单的项目可以随便用起来了,但我们既然要做到开箱即用,那就根据实际的项目发生的情况,再进一步的定制。
封装请求参数类型
this.dataOperation = {
JSON: {
headers: {
'Content-Type': 'application/json', // 告诉服务器,我们提交的数据类型为 json 格式
},
formatting(params) {
return JSON.stringify(params)
}
},
FormData: {
headers: {
'Content-Type': 'application/x-www-form-urlencoded' // 告诉服务器,我们提交的数据类型为 FormData 格式
},
formatting(params) {
let _formData = new FormData();
Object.keys(params).forEach(key => {
_formData.append(key, params[key]);
})
return _formData
}
}
}
preSend({ url, params, headers, method }) {
const { requestType } = this.config
const FetchConfig = {
...this.FetchConfig,
method,
headers: {
...this.dataOperation[requestType].headers,
...headers
},
};
if (!!params) FetchConfig.body = this.dataOperation[requestType].formatting(params);
return this.send({
url,
FetchConfig
})
}
post({ url, query, params = {}, headers }) {
return this.preSend({ url: query ? `${url}?${qs.stringify(query)}` : url, params, headers, method: 'POST' })
}
如上,我们根据策略模式 + 代理模式将发送请求报多包了一层,这样我们可以在初始化的时候,选择项目请求参数类型,一般来说一个项目并不会使用多种请求类型,所以我们暂不提供请求参数类型的方法传参配置,简化我们请求方法的参数数量。
增加 Get 请求缓存配置
一般来说,正常的 get 请求可以配置 304 缓存等,但是从网络通信请求质量跟减少请求数量来说,当数据数据过大或者请求数量过多的时候,从本地直接读取数据的性能提升会更加有效果,所以我们可以将之前代理模式里面的缓存实战例子结合 get 请求封装起来,一起搭配使用
get({ url, query, headers }) { // 优化 get 请求,添加缓存处理
const key = query ? `${url}?${qs.stringify(query)}` : url
if (this.cacheStorage) {
if (this.cacheStorage.getItem(key)) {
return Promise.resolve(this.cacheStorage.getItem(key))
} else {
return this.preSend({ url: key, headers, method: 'GET' }).then(data => {
this.cacheStorage.setItem(key, data)
return data
})
}
} else {
return this.preSend({ url: key, headers, method: 'GET' })
}
}
这边的缓存封装,是在初始化工具类的时候就已经把缓存类型跟缓存时间一起初始化完成了,这样比较方便后续业务方使用,如果全部放在参数里面的话,灵活性提高,但是方法使用成本会提高。
业务请求使用
根据之前的项目经验总结一下业务侧的使用:
- 直接将请求方法根据业务类型包一层方法,然后在需要的业务侧直接调用即可,统一处理某类请求的返回数据,数据与视图分离,利于拓展
- 将请求方法写在 vuex,redux 这种状态管理中,再去实际的业务侧调用,可以做到数据共享跨组件、页面共享
- 综合考虑使用过程中,如果请求业务不涉及跨组件、跨页面调用的时候,可以直接将业务请求写在当前代码中,这样维护起来会舒服点
尾声
完整的 demo 地址:项目实战 demo,喜欢的朋友可以 star 一下,后续会根据设计模式博文的推出,逐步的将此项目继续拓展出来。