什麼是純函式 (pure function)? 為什麼 React 的函式元件需要是純函式?
2022年2月4日
在 Hook 出現後,React 的函式元件 (Function Component) 逐漸成為主流。關於函式元件,最常見的問題之一,便是「為什麼 React 的函式元件需要是純函式 (pure function)?」。本篇將以第一人稱的方式,擬答這個問題。
什麼是純函式 (pure function)?
純函式是指帶有以下兩個主要特點的函式,第一是「只要有相同的輸入,就會有相同的輸出」;第二則是它不會改變函式以外的存在,換句話說不會有副作用 (side effects)。
舉例來說,f(x) = 2x
這個函式,只要輸入的 x 是 1,輸出的結果就會是 2;如果輸入的 x 是 2,輸出的就會是 4。不管呼叫多少次,輸出都會是一樣的,這就滿足純函式的第一個要件。相反地,假如函式當中有 Math.random()
這類隨機性的運算,會讓每次的輸出不一定,就不能稱為純函式。
沒有副作用,不改變函式以外的存在,則可以看到下面的例子 (推薦再回答這題時可以舉例,會更好說明)。當本來在外的 guest
變數,因為 getCup
函式改變,我們就不能稱 getCup
這個函式為純函式。
let guest = 1;
function sayHi(message) {
guest = guest + 1;
return `${message} 跟 ${guest} guests `;
}
為什麼 React 的函式元件需要是純函式 (pure function)?
React 的函式元件之所以需要是純函式,是因為當函式如果不純 (impure),會讓渲染出的畫面不穩定,可能出現我們預期外的 UI 呈現。白話來說,就是比較容易造成 bugs。
如果每次的輸入,會有不同的輸出,那會讓 UI 的呈現非常不穩定。以下面的程式碼為例,如果我們想要依據客人的人數,來準備巧克力派的食材,當有隨機數出現,每次傳入的客人數相同,可能會有不同的食材數量,這假如出現在食譜網站,那會是很不理想的 bug。
function ChocolatePie({ guestNum }) {
const milkNum = guestNum * Math.floor(Math.random() * 10)
const chocolateNum = guestNum * 2
return (
<div> 加入 {milkNum} 杯牛奶,以及 {chocolateNum} 條巧克力 </div>
);
}
// 如果實際渲染這三個元件,同樣的 guestNum 會渲染出不同內容
<ChocolatePie guestNum={3} />
<ChocolatePie guestNum={3} />
<ChocolatePie guestNum={3} />
如果有副作用,也將會造成不穩定的 bugs。以下面來自 React 官方範例的程式碼來說,如果更動到了 Cup
元件外的 guest
,將會導致,即使渲染同一個 Cup
元件,卻渲染出不同內容。這樣的 bug 也是不理想的。
let guest = 0;
function Cup() {
// Bad: changing a preexisting variable!
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>;
}
// 如果渲染下面這三個 Cup 元件,會有不同的結果
<Cup />
<Cup />
<Cup />
總結來說,當函式元件當中,有非純函式的存在,就會不穩定有潛在的 bug;因此在寫 React 的程式碼時,我們盡可能地寫純函式。
延伸題:如何在 React 偵測不純的函式?
在 React 常見的函式,除了函式元件,也有 setState
、context
。在使用它們時,都要確保是純函式。舉例來說,如果作為輸入傳進去元件的 props
,如果傳入同樣的 props
卻有不同的結果,就會造成不穩定的 bugs。同樣地,當我們要改變某個狀態,如果直接去改變值,而不是 setState
,會造成副作用,這也會容易產生 bugs。
要在 React 避免不小心犯了這類錯誤,可以很簡單地使用嚴格模式 (Strict Mode)。當我們開啟嚴格模式後,React 在渲染每個元件時,都渲染兩次;只要是不純的函式,就很可能在兩次渲染出現不同結果,這可以讓我們更偵測出是哪裡出問題。