
Jamie Chien • 2023-06-25
Javascript Currying(柯里化)

前言
說來慚愧,寫了這麼久的 Javascript,一直沒有好好認識認識 Curry Function,這一篇文章就要來說說什麼是 Currying,以及為什麼需要認識他。 在文章的後半段,會提供一個實際在 React 中應用 Currying 的例子,那麼廢話不多說,開始吧~
什麼是 Currying(柯里化)
引用一下維基說的話,柯里化就是『把接受多個參數的 function 轉變成多個接受單一參數的 functions』的過程。
把傳入 function 的參數,透過 closure(閉包)來存放在 function,並透過這個 function 裡面另外的 function 來回傳,來達成 currying。
e.g. 在沒有 Currying 的情況下:
function sum(a, b, c) {
return a + b + c;
}
sum(1, 2, 3); // 6
轉變成 Curry function 之後:
function sum(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
sum(1)(2)(3); //6
從上面的範例中,可以很清楚的看出兩者之間的差異,但這時候可能會很好奇,為什麼需要這樣寫呢?往下看,下面會透過一些例子來解釋一下為什麼需要 Currying(柯里化)
為什麼需要 Currying(柯里化)
簡單說有幾個好處:
- One at a Time(一次處理一個參數),每個 function 可以各自處理傳入的參數,藉此提高程式的彈性
- 也因為拆分成許多片段,所以可以更好地重複利用
這樣說可能會有點抽象,舉個例子:
假設今天要計算一個商品的售價,那剛好今天全館商品打 8 折,在沒有 Currying 的情況下的計算函式會像下面這樣
function calcDiscountPrice(price, discount) {
return price * discount;
}
calcDiscountPrice(100, 0.8); //80
calcDiscountPrice(80, 0.8); //64
...
轉成 Curry function 之後:
function calcDiscountPrice(discount) {
return function (price) {
return price * discount;
};
}
const calcWith20PercentOff = calcDiscountPrice(0.8);
calcWith20PercentOff(100); //80
calcWith20PercentOff(80); //64
...
看出差別了嗎?當我們遇到全館商品都打 8 折的情況下,就不需要每次計算價格時,都還要再帶入一次折扣,一方面減少錯誤機率,另一方面也可以提高程式擴充性
在 React 上的實際應用
用一個常見的例子來展現一下 Curry function 的好用之處啦
e.g. 在沒有使用 Curry function 的情況下,每個 onChange 事件我們都要寫一個 function 來儲存他的值,那如果我們有一大堆的 input 需要儲存呢?那我們就需要寫很攏長的程式碼來處理它
export default function App() {
const [user, setUser] = useState({
name: '',
age: '',
phone: ''
});
// First handler
const handleNameChange = (e) => {
setUser((prev) => ({
...prev,
name: e.target.value
}));
};
// Second handler
const handleAgeChange = (e) => {
setUser((prev) => ({
...prev,
age: e.target.value
}));
};
// Third handler
const handlePhoneChange = (e) => {
setUser((prev) => ({
...prev,
phone: e.target.value
}));
};
return (
<>
<input value={user.name} onChange={handleNameChange} />
<input value={user.age} onChange={handleAgeChange} />
<input value={user.phone} onChange={handlePhoneChange} />
</>
);
}
轉成 Curry function 之後,我們可以透過傳遞要修改的 field 作為第一個參數來控制要修改的值是哪個,只需要一個 curry function 就可以避免原本需要寫一堆 function 的情況了
export default function App() {
const [user, setUser] = useState({
name: '',
age: '',
phone: ''
});
const handleInputChange = (field) => {
return (e) => {
setUser((prev) => ({
...prev,
[field]: e.target.value
}));
};
};
return (
<>
<input value={user.name} onChange={handleInputChange('name')} />
<input value={user.age} onChange={handleInputChange('age')} />
<input value={user.phone} onChange={handleInputChange('phone')} />
</>
);
}
結論
雖然可能一開始看 Currying 的時候會有點母煞煞,也不知道為什麼需要他,但當寫久了之後就會發現他的好用之處,非常值得學習的一個技巧。網路上有許多實作 Curry function 的文章,LeetCode 上也有相關的題目,甚至 lodash 也有提供一個方法來達到 currying 喔!有興趣的話可以更深入的研究囉~
相關資源
- Currying for JavaScript Developers with Examples (這篇推)
- Lodash - Curry
- LeetCode - Curry
- LeetCode 上面的 Curry 需要訂閱才看得到,可以改用BFE.dev - Curry