前言
傳送地址:
設計模式已經(jīng)連載到了第 3 期,由于下一期的觀察者模式要配合團隊的埋點性能博客一起上要暫緩一段時間,所以這一篇算彩蛋篇,推出一個開箱即用型的 fetch 項目實戰(zhàn)。
封裝 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 }) {
// 發(fā)送 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)
})
如上我們簡單的封裝了一個可以發(fā)送 get、 post 請求的 fetch 類,加入了超時跟網(wǎng)關,簡單的項目可以隨便用起來了,但我們既然要做到開箱即用,那就根據(jù)實際的項目發(fā)生的情況,再進一步的定制。
封裝請求參數(shù)類型
this.dataOperation = {
JSON: {
headers: {
'Content-Type': 'application/json', // 告訴服務器,我們提交的數(shù)據(jù)類型為 json 格式
},
formatting(params) {
return JSON.stringify(params)
}
},
FormData: {
headers: {
'Content-Type': 'application/x-www-form-urlencoded' // 告訴服務器,我們提交的數(shù)據(jù)類型為 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' })
}
如上,我們根據(jù)策略模式 + 代理模式將發(fā)送請求報多包了一層,這樣我們可以在初始化的時候,選擇項目請求參數(shù)類型,一般來說一個項目并不會使用多種請求類型,所以我們暫不提供請求參數(shù)類型的方法傳參配置,簡化我們請求方法的參數(shù)數(shù)量。
增加 Get 請求緩存配置
一般來說,正常的 get 請求可以配置 304 緩存等,但是從網(wǎng)絡通信請求質量跟減少請求數(shù)量來說,當數(shù)據(jù)數(shù)據(jù)過大或者請求數(shù)量過多的時候,從本地直接讀取數(shù)據(jù)的性能提升會更加有效果,所以我們可以將之前代理模式里面的緩存實戰(zhàn)例子結合 get 請求封裝起來,一起搭配使用
get({ url, query, headers }) { // 優(yōu)化 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' })
}
}
這邊的緩存封裝,是在初始化工具類的時候就已經(jīng)把緩存類型跟緩存時間一起初始化完成了,這樣比較方便后續(xù)業(yè)務方使用,如果全部放在參數(shù)里面的話,靈活性提高,但是方法使用成本會提高。
業(yè)務請求使用
根據(jù)之前的項目經(jīng)驗總結一下業(yè)務側的使用:
- 直接將請求方法根據(jù)業(yè)務類型包一層方法,然后在需要的業(yè)務側直接調(diào)用即可,統(tǒng)一處理某類請求的返回數(shù)據(jù),數(shù)據(jù)與視圖分離,利于拓展
- 將請求方法寫在 vuex,redux 這種狀態(tài)管理中,再去實際的業(yè)務側調(diào)用,可以做到數(shù)據(jù)共享跨組件、頁面共享
- 綜合考慮使用過程中,如果請求業(yè)務不涉及跨組件、跨頁面調(diào)用的時候,可以直接將業(yè)務請求寫在當前代碼中,這樣維護起來會舒服點
尾聲
完整的 demo 地址:項目實戰(zhàn) demo,喜歡的朋友可以 star 一下,后續(xù)會根據(jù)設計模式博文的推出,逐步的將此項目繼續(xù)拓展出來。