- 原文地址:A practical guide to writing more functional JavaScript
- 原文作者:Nadeesha Cabral
- 本文永久連結:github-heyushuo-blob
- 譯者:heyushuo
一切皆為函式
函數語言程式設計很棒。隨著 React 的引入,越來越多的 JavaScript 前端程式碼正在考慮 FP 原則。但是我們如何在我們編寫的日常程式碼中開始使用 FP 思維模式?我將嘗試使用日常程式碼塊並逐步重構它。
我們的問題:使用者來到我們的登入頁面連結後會帶一個redirect_to
引數。就像/login?redirect_to =%2Fmy-page
。請注意,當%2Fmy-page
被編碼為 URL
的一部分時,它實際上是/ my-page
。我們需要提取此引數,並將其儲存在本地儲存中,以便在完成登入後,可以將使用者重定向到 my-page
頁面。
第 0 步:必要的方法
如果我們以最簡單方式來呈現這個解決方案,我們將如何編寫它?我們需要如下幾個步驟
- 解析連結後引數。
- 獲取 redirect_to 值。
- 解碼該值。
- 將解碼後的值儲存在 localStorage 中。
我們還必須將不安全
的函式放到try catch
塊中。有了這些,我們的程式碼將如下所示:
function persistRedirectToParam() {
let parsedQueryParam;
try {
//獲取連線後的引數{redirect_to:'/my-page'}
parsedQueryParam = qs.parse(window.location.search); // https://www.npmjs.com/package/qs
} catch (e) {
console.log(e);
return null;
}
//獲取到引數
const redirectToParam = parsedQueryParam.redirect_to;
if (redirectToParam) {
const decodedPath = decodeURIComponent(redirectToParam);
try {
localStorage.setItem('REDIRECT_TO', decodedPath);
} catch (e) {
console.log(e);
return null;
}
//返回 my-page
return decodedPath;
}
return null;
}
複製程式碼
第 1 步:將每一步寫為函式
暫時,讓我們忘記 try catch
塊並嘗試將所有內容表達為函式。
// // 讓我們宣告所有我們需要的函式
const parseQueryParams = query => qs.parse(query);
const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;
const decodeString = string => decodeURIComponent(string);
const storeRedirectToQuery = redirectTo => localStorage.setItem('REDIRECT_TO', redirectTo);
function persistRedirectToParam() {
//使用它們
const parsed = parseQueryParams(window.location.search);
const redirectTo = getRedirectToParam(parsed);
const decoded = decodeString(redirectTo);
storeRedirectToQuery(decoded);
return decoded;
}
複製程式碼
當我們開始將所有“結果”用函式的方式表示時,我們會看到我們可以從主函式體中重構的內容。這樣處理後,我們的函式變得更容易理解,並且更容易測試。
早些時候,我們將測試主要函式作為一個整體。但是現在,我們有 4 個較小的函式,其中一些只是代理其他函式,因此需要測試的足跡要小得多。
讓我們識別這些代理函式,並刪除代理,這樣我們就可以減少一些程式碼。
const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo => localStorage.setItem('REDIRECT_TO', redirectTo);
function persistRedirectToParam() {
const parsed = qs.parse(window.location.search);
const redirectTo = getRedirectToParam(parsed);
const decoded = decodeURIComponent(redirectTo);
storeRedirectToQuery(decoded);
return decoded;
}
複製程式碼
第 2 步 嘗試編寫函式式
好的。現在,似乎 persistRedirectToParam
函式是 4 個其他函式的“組合”讓我們看看我們是否可以將此函式編寫為合成,從而消除我們儲存為 const
的中間結果。
const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to;
// we have to re-write this a bit to return a result.
const storeRedirectToQuery = (redirectTo) => {
localStorage.setItem("REDIRECT_TO", redirectTo)
return redirectTo;
};
function persistRedirectToParam() {
const decoded = storeRedirectToQuery(
decodeURIComponent(
getRedirectToParam(
qs.parse(window.location.search)
)
)
)
return decoded;
}
複製程式碼
這很好。但是我同情讀取這個巢狀函式呼叫的人。如果有辦法解開這個混亂,那就太棒了。
第 3 步 更具可讀性的組合
如果你已經完成了以上的一些重構,那麼你就會遇到compose
。Compose
是一個實用函式,它接受多個函式,並返回一個逐個呼叫底層函式的函式。還有其他很好的資源來學習 composition
,所以我不會在這裡詳細介紹。
使用 compose
,我們的程式碼將如下所示:
const compose = require('lodash/fp/compose');
const qs = require('qs');
const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo => {
localStorage.setItem('REDIRECT_TO', redirectTo);
return redirectTo;
};
function persistRedirectToParam() {
const op = compose(
storeRedirectToQuery,
decodeURIComponent,
getRedirectToParam,
qs.parse
);
return op(window.location.search);
}
複製程式碼
compose 內的函式執行順序為從右向左,即最右邊的函式(最後一個引數)最先執行,執行完的結果作為引數傳遞給前一個函式。因此,在 compose 鏈中呼叫的第一個函式是最後一個函式。
如果你是一名數學家並且熟悉這個概念,這對你來說不是一個問題,所以你自然會從右到左閱讀。但對於熟悉命令式程式碼的其他人來說,我們想從左到右閱讀。
第 4 步 pipe(管道)和扁平化
幸運的是這裡有pipe(管道)
和 compose
做了同樣的事情,但是執行順序和 compose
是相反的,因此鏈中的第一個函式最先執行,執行完的結果作為引數傳遞給下一個函式。
而且,似乎我們的 persistRedirectToParams
函式已經成為另一個我們稱之為 op
的函式的包裝器。換句話說,它所做的只是執行op
。我們可以擺脫包裝並“扁平化”我們的函式。
const pipe = require('lodash/fp/pipe');
const qs = require('qs');
const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo => {
localStorage.setItem('REDIRECT_TO', redirectTo);
return redirectTo;
};
const persistRedirectToParam = fp.pipe(
qs.parse,
getRedirectToParam,
decodeURIComponent,
storeRedirectToQuery
);
複製程式碼
差不多了。請記住,我們適當地將 try-catch 塊留在後面,以使其達到正確的狀態?好的接下來,我們需要一些方式來介紹它。qs.parse 和 storeRedirectToQuery 都是不安全。一種選擇是使它們成為包裝函式並將它們放在 try-catch 塊中。另一種函式式方式
是將 try-catch
表示為一種函式。
第 5 步 作為函式的異常處理
有一些實用程式做到了這一點,但讓我們自己嘗試寫一些東西。
function tryCatch(opts) {
return args => {
try {
return opts.tryer(args);
} catch (e) {
return opts.catcher(args, e);
}
};
}
複製程式碼
我們的函式在這裡需要一個包含 tryer 和 catcher 函式的 opts 物件。它將返回一個函式,當使用引數呼叫時,使用所述引數呼叫 tryer 並在失敗時呼叫 catcher。現在,當我們有不安全的操作時,我們可以將它們放入 tryer 部分,如果它們失敗,則從捕獲器部分進行救援並提供安全結果(甚至記錄錯誤)。
第 6 步 把所有東西放在一起
因此,考慮到這一點,我們的最終程式碼如下:
const pipe = require('lodash/fp/pipe');
const qs = require('qs');
const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo => {
localStorage.setItem('REDIRECT_TO', redirectTo);
return redirectTo;
};
const persistRedirectToParam = fp.pipe(
tryCatch({
tryer: qs.parse,
catcher: () => {
return {
redirect_to: null // we should always give back a consistent result to the subsequent function
};
}
}),
getRedirectToParam,
decodeURIComponent,
tryCatch({
tryer: storeRedirectToQuery,
catcher: () => null // if localstorage fails, we get null back
})
);
// to invoke, persistRedirectToParam(window.location.search);
複製程式碼
這或多或少是我們想要的。但是為了確保程式碼的可讀性和可測試性得到改善,我們也可以將“安全”函式(tryCatch 函式)分解出來。
const pipe = require('lodash/fp/pipe');
const qs = require('qs');
const getRedirectToParam = parsedQuery => parsedQuery.redirect_to;
const storeRedirectToQuery = redirectTo => {
localStorage.setItem('REDIRECT_TO', redirectTo);
return redirectTo;
};
const safeParse = tryCatch({
tryer: qs.parse,
catcher: () => {
return {
redirect_to: null // we should always give back a consistent result to the subsequent function
};
}
});
const safeStore = tryCatch({
tryer: storeRedirectToQuery,
catcher: () => null // if localstorage fails, we get null back
});
const persistRedirectToParam = fp.pipe(
safeParse,
getRedirectToParam,
decodeURIComponent,
safeStore
);
複製程式碼
現在,我們得到的是一個更強大功能的函式,由 4 個獨立的函式組成,這些函式具有高度內聚性,鬆散耦合,可以獨立測試,可以獨立重用,考慮異常場景,並且具有高度宣告性。
有一些 FP 語法糖使這變得更好,但是這是以後的某一天。
如果發現譯文存在錯誤或其他需要改進的地方請指出。