Pure function 은 다음과 같은 특성을 지니는 함수이다.
예를 들어, 다음 double
함수는 pure function이다.
function double(number) {
return 2 * number;
}
왜 갑자기 pure function에 대해 언급할까? 그 이유는, React는 모든 component가 pure function이라는 가정하에 작동하기 때문이다. 다시 말해, React component는 같은 input에 대해 항상 같은 JSX를 반환해야 한다.
💡 React assumes that every component you write is a pure function.
만약 component가 pure function이 아니라면 어떻게 될까?
let guest = 0;
function Cup() {
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>;
}
Cup
component는 매 실행마다 guest
변수를 수정한다. 따라서 Cup
은 매 render마다 다른 JSX를 반환한다.
Component가 render되는 순서는 예측할 수 없다. 따라서 "pure"하지 않은 component는 예상과 다른 반환을 가져올 수 있다. 따라서, 각 component는 다른 component와 독립적으로, "pure"하게 작동해야 한다.
💡 Each component should only “think for itself”, and not attempt to coordinate with or depend upon others during rendering.
이를 위해, 우리는 prop을 활용한다. 위의 Cup
component에 guest
를 prop으로 전달함으로써 pure function으로 바꿀 수 있다.
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}
• Detecting impure calculations with StrictMode
CRA를 통해 프로젝트를 구성 시, <React.StrictMode>
라는 태그를 본 기억이 있을 것이다. React는 모든 function component를 두번 호출되게 만드는 "Strict Mode"를 제공한다. 앞서 말했듯이, inpure한 component의 경우 호출마다 다른 UI를 render할 것이므로, 이를 통해 잘못 설계된 component를 보다 쉽게 찾아낼 수 있다.
Root component를 <React.StrictMode>
로 감싸 사용할 수 있으며, Strict Mode의 경우 production 시 무시되므로 성능 걱정은 따로 할 필요가 없다.
• Local mutation: Your component's little secret
위의 수정 전 Cup
component의 문제점은 component 외부의 변수를 내부에서 바꾼다는 점이다. 이는 mutation 이라고 부르며, pure function 내에서는 function scope 외부의 변수의 수정, 즉 mutation이 발생하지 않는다.
이는 다른 관점에서 봤을 때, function scope 내부의 변수의 수정은 pure function의 조건에 위배되지 않는 다는 것이다.
💡 It’s completely fine to change variables and objects that you’ve just created while rendering.
다음은 수정된 Cup
component의 목록을 출력하는 TeaGathering
component이다.
export default function TeaGathering() {
let cups = [];
for (let i = 1; i <= 12; i++) {
cups.push(<Cup key={i} guest={i} />);
}
return cups;
}
비록 TeaGathering
component에서 cups
변수를 수정하는 local mutation이 발생하고 있지만 해당 component 외부의 다른 곳에서는 아무도 이 사실을 알 수 없다.
😉 It's like your component's little secret!
그렇다면 component의 render 이후에 발생해야 하는 것들, 다시 말해 side effects들은 어떻게 처리해야할까?
특정 component를 클릭했을 때 발생하는 side effect 등과 같이 React에서 대부분의 side effect들은 event handler를 통해 처리된다. 비록, event handler들 역시 component 내부에 정의되어야 하지만 render시에 실행되지 않으므로 (올바르게 구현했다면..) event handler들은 pure function일 필요는 없다.
또한 useEffect
를 이용해서 side effect의 처리가 가능하나 이는 최후의 선택이 되어야 한다. 해당 내용의 디테일은 이후 포스팅에서 다룬다.
• Why does React care about purity?
React가 purity를 고집하여 얻을 수 있는 장점은 무엇일까?
Your components could run in a different environment - 예를 들어, server에서 실행된다고 했을 때, purity가 보장된 component는 여러 user request에 대해 동일한 값을 반환한다.
You can imporve performance by skipping rendering - Purity가 보장된 component는 같은 입력에 대해 같은 출력을 발생시키므로 후에 다룰 함수들을 통해 memoization이 가능하다.
React can restart rendering any time - render 도중 data의 변화가 생겨도, 어떤 시점에서도 function의 재실행이 가능해진다.
💡 From data fetching to animations to performance, keeping components pure unlocks the power of the React paradigm.