停止編寫(xiě) API函數(shù)原因示例分析
正文
RESTFUL API 通常提供在不同實(shí)體上執(zhí)行增刪改查(CRUD)操作的一組接口。我們通常在我們的前端項(xiàng)目中為這些每一個(gè)接口提供一個(gè)函數(shù),這些函數(shù)的功能非常的相似,只是為了服務(wù)于不用的實(shí)體。舉個(gè)例子,假設(shè)我們有這些函數(shù)。
// api/users.js // 創(chuàng)建 export function createUser(userFormValues) { return fetch('users', { method: 'POST', body: userFormValues }); } // 查詢 export function getListOfUsers(keyword) { return fetch(`/users?keyword=${keyword}`); } export function getUser(id) { return fetch(`/users/${id}`); } // 更新 export updateUser(id, userFormValues) { return fetch(`/users/${is}`, { method: 'PUT', body: userFormValues }); } // 刪除 export function removeUser(id) { return fetch(`/users/${id}`, { method: 'DELETE' }); }
類似的功能可能存在于其他實(shí)體,例如:城市、產(chǎn)品、類別...但是我們可以用一個(gè)簡(jiǎn)單的函數(shù)調(diào)用來(lái)代替這些函數(shù):
// apis/users.js export const users = crudBuilder('/users'); // apis/cities.js export const cities = crudBuilder('/regions/cities');
然后像這樣去使用:
users.create(values); users.show(1); users.list('john'); users.update(values); users.remove(1);
你可能會(huì)問(wèn)為什么?有一些很好的理由:
- 減少了代碼行數(shù):你編寫(xiě)的代碼,和當(dāng)你離開(kāi)公司時(shí)其他人維護(hù)的代碼
- 強(qiáng)制執(zhí)行 API 函數(shù)的命名約定,這可以增加代碼的可讀性和可維護(hù)性。例如你已經(jīng)見(jiàn)過(guò)的函數(shù)名稱:
getListOfUsers
,getCities
,getAllProducts
,productIndex
,fetchCategories
等, 他們都在做相同的事情,那就是“獲取實(shí)體列表”。使用這種方法,你將始終擁有entityName.list()
函數(shù),并且團(tuán)隊(duì)中的每個(gè)人都知道這一點(diǎn)。
所以,讓我們創(chuàng)建crudBuilder()
函數(shù),然后再添加一些糖。
一個(gè)非常簡(jiǎn)單的 CRUD 構(gòu)造器
對(duì)于上邊的簡(jiǎn)單示例,crudBuilder()
函數(shù)將非常簡(jiǎn)單:
export function crudBuilder(baseRoute) { function list(keyword) { return fetch(`${baseRoute}?keyword=${keyword}`); } function show(id) { return fetch(`${baseRoute}/${id}`); } function create(formValues) { return fetch(baseRoute, { method: 'POST', body: formValues }); } function update(id, formValues) { return fetch(`${baseRoute}/${id}`, { method: 'PUT', body: formValues }); } function remove(id) { return fetch(`${baseRoute}/${id}`, { method: 'DELETE' }); } return { list, show, create, update, remove }; }
假設(shè)約定 API 路徑并且給相應(yīng)實(shí)體提供一個(gè)路徑前綴,他將返回該實(shí)體上調(diào)用 CRUD 操作所需的所有方法。
但老實(shí)說(shuō),我們知道現(xiàn)實(shí)世界的應(yīng)用程序并不會(huì)那么簡(jiǎn)單。在將這種方法應(yīng)用于我們的項(xiàng)目時(shí),有很多事情需要考慮:
- 過(guò)濾:列表 API 通常會(huì)提供許多過(guò)濾器參數(shù)
- 分頁(yè):列表 API 總是分頁(yè)的
- 轉(zhuǎn)換:API 返回的值在實(shí)際使用之前可能需要進(jìn)行一些轉(zhuǎn)換
- 準(zhǔn)備:
formValues
對(duì)象在發(fā)送給 API 之前需要做一些準(zhǔn)備工作 - 自定義接口:更新特定項(xiàng)的接口不總是
${baseRoute}/${id}
因此,我們需要可以處理更多復(fù)雜場(chǎng)景的 CRUD 構(gòu)造器。
高級(jí) CRUD 構(gòu)造器
讓我們通過(guò)上述方法來(lái)構(gòu)建一些日常中我們真正使用的東西。
過(guò)濾
首先,我們應(yīng)該在 list
輸出函數(shù)中處理更加復(fù)雜的過(guò)濾。每個(gè)實(shí)體列表可能有不同的過(guò)濾器并且用戶可能應(yīng)用了其中的一些過(guò)濾器。因此,我們不能對(duì)應(yīng)用過(guò)濾器的形狀和值有任何假設(shè),但是我們可以假設(shè)任何列表過(guò)濾都可以產(chǎn)生一個(gè)對(duì)象,該對(duì)象為不同的過(guò)濾器名稱指定了一些值。例如,我們可以過(guò)濾一些用戶:
const filters = { keyword: 'john', createdAt: new Date('2020-02-10') };
另一方面,我們不知道這些過(guò)濾器應(yīng)該如何傳遞給 API,但是我們可以假設(shè)(跟 API 提供方進(jìn)行約定)每一個(gè)過(guò)濾器在列表 API 中都有一個(gè)相應(yīng)的參數(shù),可以以'key=value'
URL 查詢參數(shù)的形式被傳遞。
因此我們需要知道如何將應(yīng)用的過(guò)濾器轉(zhuǎn)換成相對(duì)應(yīng)的 API 參數(shù)來(lái)創(chuàng)建我們的 list
函數(shù)。這可以通過(guò)將 transformFilters
參數(shù)傳遞給 crudBuilder()
來(lái)完成。舉一個(gè)用戶的例子:
function transformUserFilters(filters) { const params = []; if (filters.keyword) { params.push(`keyword=${filters.keyword}`); } if (filters.createdAt) { params.push(`create_at=${dateUtility.format(filters.createdAt)}`); } return params; }
現(xiàn)在我們可以使用這個(gè)參數(shù)來(lái)創(chuàng)建 list
函數(shù)了。
export function crudBuilder(baseRoute, transformFilters) { function list(filters) { let params = transformFilters(filters)?.join('&'); if (params) { params += '?'; } return fetch(`${baseRoute}${params}`); } }
轉(zhuǎn)換和分頁(yè)
從 API 接收的數(shù)據(jù)可能需要進(jìn)行一些轉(zhuǎn)換才能在我們的應(yīng)用程序中使用。例如,我們可能需要將 snake_case
轉(zhuǎn)換成駝峰命名或?qū)⒁恍┤掌谧址D(zhuǎn)換成用戶時(shí)區(qū)。
此外,我們還需要處理分頁(yè)。
我們假設(shè)來(lái)自 API 的分頁(yè)數(shù)據(jù)都按照如下格式(與 API 提供者約定):
{ data: [], // 實(shí)體對(duì)象列表 pagination: {...} // 分頁(yè)信息 }
因此,我們需要知道如何轉(zhuǎn)換單個(gè)實(shí)體對(duì)象。然后我們可以遍歷列表對(duì)象來(lái)轉(zhuǎn)換他們。為此,我們需要一個(gè) transformEntity
函數(shù)作為 crudBuilder
的參數(shù)。
export function crudBuilder(baseRoute, transformFilters, transformEntity, ) { function list(filters) { const params = transformFilters(filters)?.join('&'); return fetch(`${baseRoute}?${params}`) .then((res) => res.json()) .then((res) => ({ data: res.data.map((entity) => transformEntity(entity)), pagination: res.pagination })); } }
list()
函數(shù)我們就完成了。
準(zhǔn)備
對(duì)于 create
和 update
函數(shù),我們需要將 formValues
轉(zhuǎn)換成 API 需要的格式。例如,假設(shè)我們?cè)诒韱沃杏幸粋€(gè) City
的城市選擇對(duì)象。但是 create
API 只需要 city_id
。因此,我們需要一個(gè)執(zhí)行以下操作的函數(shù):
const prepareValue = formValue => ({city_id: formValues.city.id});
這個(gè)函數(shù)會(huì)根據(jù)用例返回普通對(duì)象或者 FormData
,并且可以將數(shù)據(jù)傳遞給 API:
export function crudBuilder(baseRoute, transformFilters, transformEntity, prepareFormValues) { function create(formValues) { return fetch(baseRoute, { method: 'POST', body: prepareFormValues(formValues) }); } }
自定義接口
在一些少數(shù)情況下,對(duì)實(shí)體執(zhí)行某些操作的 API 接口不遵循相同的約定。例如,我們不能使用 /users/${id}
來(lái)編輯用戶,而是使用 /edit-user/${id}
。對(duì)于這些情況,我們應(yīng)該指定一個(gè)自定義路徑。
在這里我們?cè)试S覆蓋 crud builder 中使用的任何路徑。注意,展示、更新、移除操作的路徑可能取決于具體實(shí)體對(duì)象的信息,因此我們必須使用函數(shù)并傳遞實(shí)體對(duì)象來(lái)獲取路徑。
我們需要在對(duì)象中獲取這些自定義路徑,如果沒(méi)有指定,就退回到默認(rèn)路徑。像這樣:
const paths = { list: 'list-of-users', show: (userId) => `users/with/id/${userId}`, create: 'users/new', update: (user) => `users/update/${user.id}`, remove: (user) => `delete-user/${user.id}` };
最終的 BRUD 構(gòu)造器
這是創(chuàng)建 CRUD 函數(shù)的最終代碼。
export function crudBuilder(baseRoute, transformFilters, transformEntity, prepareFormValues, paths) { function list (filters) { const path = paths.list || baseRoute; let params = transformFilters(filters)?.join('&'); if (params) { params += '?'; } return fetch(`${path}${params}`) .then((res) => res.json()) .then(() => ({ data: res.data.map(entity => transformEntity(entity)), pagination: res.pagination })); } function show(id) { const path = paths.show?.(id) || `${baseRoute}/${id}`; return fetch(path) .then((res) => res.json()) .then((res => transformEntity(res))); } function create(formValues) { const path = paths.create || baseRoute; return fetch(path, { method: 'POST', body: prepareFormValues(formValues) }); } function update(id, formValues) { const path = paths.update?.(id) || `${baseRoute}/${id}`; return fetch(path, { method: 'PUT', body: formValues }); } function remove(id) { const path = paths.remove?.(id) || `${baseRoute}/${id}`; return fetch(path, { method: 'DELETE' }); } return { list, show, create, update, remove } }
Saeed Mosavat: Stop writing API functions
以上就是停止編寫(xiě) API函數(shù)原因示例分析的詳細(xì)內(nèi)容,更多關(guān)于停止編寫(xiě) API 函數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js實(shí)現(xiàn)保存文本框內(nèi)容為本地文件兼容IE,chrome,火狐瀏覽器
本文實(shí)現(xiàn)了利用JS保存頁(yè)面中文本框內(nèi)容到本地,并另存為指定文件擴(kuò)展名與編碼類型,兼容IE,chrome,火狐等瀏覽器2018-02-02JavaScript自動(dòng)化測(cè)試添加頁(yè)面DOM元素唯一ID方案示例
這篇文章主要為大家介紹了JavaScript自動(dòng)化測(cè)試添加頁(yè)面DOM元素唯一ID方案示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09JS屬性scrollTop?clientHeight?scrollHeight理解學(xué)習(xí)
這篇文章主要為大家介紹了JS屬性scrollTop?clientHeight?scrollHeight理解學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07