什么是纯函式 (pure function)? 为什么 React 的函式元件需要是纯函式?

2022年2月4日

💎 加入 E+ 成長計畫 與超過 350+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

在 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 常见的函式,除了函式元件,也有 setStatecontext 。在使用它们时,都要确保是纯函式。举例来说,如果作为输入传进去元件的 props,如果传入同样的 props 却有不同的结果,就会造成不稳定的 bugs。同样地,当我们要改变某个状态,如果直接去改变值,而不是 setState ,会造成副作用,这也会容易产生 bugs。

要在 React 避免不小心犯了这类错误,可以很简单地使用严格模式 (Strict Mode)。当我们开启严格模式后,React 在渲染每个元件时,都渲染两次;只要是不纯的函式,就很可能在两次渲染出现不同结果,这可以让我们更侦测出是哪里出问题。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們