해당 내용은 2022년 11월 08일 React Docs BETA 버전을 토대로 작성되었습니다. -링크
Purity - 순도
일부 JavaScript 함수는 순수하며 컴포넌트를 순수 함수로 작성하면 코드 베이스가 커짐에 따라 마주치는 당황스러운 버그와 예측하기 힘든 동작을 피할 수 있다.
순수 함수의 한 예인 수학 공식에 이미 익숙할 것이며 수학 공식을 예로 생각해보자.
y = 2x 일때.
x = 2 이면 y = 4
x = 3 이면 y = 6
x = 3 이면 y는 항상 6이다. 이것을 JavaScript로 작성하면 아래와 같다. double은 순수 함수이다.
function double(number) {
return 2 * number;
}
React는 위와 같은 개념을 중심으로 설계되었으며 작성하는 모든 컴포넌트들은 순수 함수라고 가정한다.
즉, React 컴포넌트는 동일한 입력이 주어지면 항상 동일한 JSX를 반환해야 한다.
function Recipe({ drinkers }) {
return (
<ol>
<li>Boil {drinkers} cups of milk.</li>
<li>Add {2 * drinkers} spoons of masala spices.</li>
<li>Remove from heat, and add {drinkers} spoons of tea.</li>
</ol>
);
}
export default function App() {
return (
<section>
<h1>Spiced Chai Recipe</h1>
<h2>For one</h2>
<Recipe drinkers={1} />
<h2>For a gathering</h2>
<Recipe drinkers={4} />
</section>
);
}
위 코드를 보면 Recipe 컴포넌트에게 props를 넘겨줄 때 drinkers가 1이거나 4이면 항상 그에 맞는 JSX를 반환할 것이다.
Side Effect: (un)intended consequences - 의도하지 않은 결과
React의 렌더링 과정은 항상 순수해야 한다. 컴포넌트는 JSX만 반환해야 하며 렌더링 전에 존재했던 객체나 변수를 변경해서는 안된다.
let guest = 0;
function Cup() {
// Bad: changing a preexisting variable!
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup />
<Cup />
</>
)
}
위 코드를 보면 Cup컴포넌트는 외부에 선언된 변수를 읽고 쓰고 있기에 순수하지 않다.
앞서 말했던 공식 y =2x로 돌아가서 이제 x = 2 일지라도 y = 4 임을 신뢰할 수 없게 되었다.
이것을 해결하기 위해 props로 전달해줄 수 있다.
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
</>
);
}
반환하는 JSX는 props에게만 의존하였기에 해당 컴포넌트는 순수하다.
일반적으로 컴포넌트가 특정 순서를 가지고 렌더링 될 것이라고 생각해서는 안된다. 각 컴포넌트는 "스스로 생각"해야 하며 렌더링 중에 다른 컴포넌트와 조정하거나 의존해서는 안된다.
순수 함수는 함수 범위 밖의 변수나 호출 전에 생성된 객체를 변경하지 않지만 렌더링 하는 동안 방금 만든 변수와 객체를 변경하는 것은 전혀 문제가 되지 않는다. 이것을 예시로 살펴보자.
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaGathering() {
let cups = [];
for (let i = 1; i <= 12; i++) {
cups.push(<Cup key={i} guest={i} />);
}
return cups;
}
변수 cups나 [] 배열이 TeaGathering 함수 외부에서 생성된다면 큰 문제가 되지만 동일한 렌더링을 수행하는 TeaGathering 함수 내부에서 생성했기 때문에 괜찮다. 외부에서는 알 수가 없으며 이것을 "local mutation"이라고 한다.
이처럼 함수형 프로그래밍, React의 컴포넌트는 Purity에 크게 의존하지만 어딘가에서 무언가가 변경되어야 할 필요가 있다. 이러한 변경(화면 업데이트, 애니메이션 시작, 데이터 변경)을 Side Effect(부작용)라고 한다.
렌더링 중이 아닌 "on the side" 즉, 측면에서 발생하는 것이다.
React에서 Side Effect는 일반적으로 이벤트 헨들러 내부에 속한다. 이벤트 헨들러는 컴포넌트 내부에 정의되어 있긴 하지만 렌더링 중에는 실행되지 않는다. 따라서 이벤트 헨들러는 순수할 필요가 없다.
다른 옵션들을 모두 사용했는데 Side Effect에 적합한 이벤트 헨들러를 찾을 수 없다면, useEffect를 사용하여 반환된 JSX에 계속 연결할 수 있다. 이는 렌더링 후 Side Effect가 허용될 때 나중에 실행하도록 React에 지시한다. 하지만 가능한 렌더링 만으로 로직을 표현하는 것이 좋다.
export default function Clock({ time }) {
let hours = time.getHours();
if (hours >= 0 && hours <= 6) {
document.getElementById('time').className = 'night';
} else {
document.getElementById('time').className = 'day'; // Runtime Error
}
return (
<h1 id="time">
{time.toLocaleTimeString()}
</h1>
);
}
시간을 받아와서 자정부터 오전 6시까지는 night로 나머지는 day로 하여 h1요소의 CSS 클래스를 설정해주려 하고 있다. 하지만 런타임 에러가 발생한다. 무엇이 문제일까?
렌더링은 계산일 뿐, 무언가를 하려고 하면 안 된다. 리펙토링 해보자.
export default function Clock({ time }) {
let hours = time.getHours();
let className;
if (hours >= 0 && hours <= 6) {
className = 'night';
} else {
className = 'day';
}
return (
<h1 className={className}>
{time.toLocaleTimeString()}
</h1>
);
}
위처럼 작성해야 올바른 코드이다.
내용 요약
- 컴포넌트는 순수해야 한다.
- 렌더링 전에 존재했던 객체나 변수를 변경해서는 안되며 자신의 일을 스스로 생각해야 한다.
- 동일한 입력엔 동일한 출력. 컴포넌트는 항상 동일한 JSX를 반환해야 한다.
- 렌더링은 언제든지 발생할 수 있기에 컴포넌트는 서로의 렌더링 순서에 의존해서는 안된다.
- 컴포넌트가 렌더링에 사용하는 입력을 변경해서는 안된다. (props, state, and context)
- 화면을 업데이트하려면 기존 객체를 변경하는 것이 아닌 "set"해야 한다.
- 반환하는 JSX에서 컴포넌트의 로직을 표현하려고 노력해야 하고, 변경이 필요한 경우 일반적으로 이벤트 핸들러에서 할 수 있기에 마지막 수단으로 useEffect를 사용하자.
순수 함수를 작성함으로써 얻을 수 있는 장점
동일한 입력에 동일한 결과를 반환하기에 하나의 컴포넌트에서 많은 사용자 요청, 즉 데이터를 처리할 수 있다.
입력이 변경되지 않는 컴포넌트들의 리렌더링을 막아 성능을 향상 시킬 수 있다.
순수 함수는 항상 동일한 결과를 반환하기에 안전하고, 캐시해도 안전하다.
깊은 컴포넌트 트리를 렌더링하는 동안 일부 데이터가 변경되면 React는 오래된 렌더링을 완료하는 데에 시간을 낭비하지 않고 리렌더링 할 수 있다. 순도는 언제든지 연산을 중단하는 것을 안전하게 만든다.
모든 새로운 React의 기능은 순수성을 활용하며, data fetching, animations, performance에 이르기까지 컴포넌트를 순수하게 유지하면 React paradigm의 힘이 발휘될 것이다.
'개발자의 공부 > React' 카테고리의 다른 글
[React]Google 로그인 OAuth2로 다른 Google API와 연동 (0) | 2022.11.27 |
---|---|
[React]useContext (0) | 2022.11.15 |
[React]useEffect (0) | 2022.11.08 |
[React]useReducer (0) | 2022.10.24 |
[React]State(내용 업데이트) (0) | 2022.10.24 |