
일부 JavaScript 함수는 순수하다. 순수 함수는 계산만 수행하고 그 이상은 수행하지 않는다. 컴포넌트를 엄격하게 순수 함수로만 작성하게 된다면 우리는 당황스러운 버그와 예측할 수 없는 동작을 피할 수 있다.
하지만 이러한 이점을 얻으려면 몇 가지 규칙을 준수해야하는데, 이제부터 알아보도록 하자.
순수성은 무엇일까?
순수 함수는 다음과 같은 특징을 갖는다.
- 자신의 일에만 신경쓰고 호출되기 전 존재했던 객체나 변수를 변경하지 않는다.
- 동일한 입력이 들어오면 동일한 출력을 반환한다.
우리는 순수 함수를 이미 알고있다.
예를 들면 수학 공식에서
y = 2x를 생각해보자.
x에 같은 입력을 전달하면 항상 동일한 출력이 반환됨을 우리는 이미 알고 있다.
React는 이 개념을 중심으로 설계되었다. React는 우리가 작성하는 모든 컴포넌트가 순수 함수라고 가정한다. 즉, 우리가 작성하는 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 />
</>
);
}
이 코드의 출력은 아래와 같다.
Tea cup for guest #2
Tea cup for guest #4
Tea cup for guest #6
이 컴포넌트는 외부에서 선언된 guest 변수를 읽고 쓰고 있기 때문에 호출될 때마다 다른 JSX가 생성된다! 게다가 다른 컴포넌트가 guest를 읽게 되면 렌더링된 시점에 따라 JSX도 다르게 생성되기 때문에 우리는 결과를 예측할 수 없게된다.
우리는 guest를 prop으로 전달해 이 컴포넌트를 정상적으로 동작하게 할 수 있다.
이제 컴포넌트가 반환하는 JSX는 guest prop에만 의존하므로 순수하다.
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}
StrictMode로 순수하지 않은 계산 감지하기
React에서는 렌더링하는 동안 읽을 수 있는 입력이 props, state, context 세 가지가 있다. 이러한 입력은 항상 읽기 전용으로 취급해야 한다.
사용자 입력에 대한 응답으로 무언가 변경하고자 한다면 변수에 쓰는 대신 state를 설정해야 한다. 또, 컴포넌트가 렌더링되는 동안에는 기존 변수나 객체를 절대 변경해서는 안된다.
Reace는 개발 환경에서 각 컴포넌트의 함수를 두 번 호출하는 "Strict Mode"를 제공한다. Strict Mode는 컴포넌트 함수를 두 번 호출함으로써 이러한 규칙을 위반하는 컴포넌트를 찾아내는 데 도움이 된다.
위의 “Guest #2”, “Guest #4”, “Guest #6”으로 출력된 결과는 두 번 호출되었기 때문에 예상과 다른 결과가 나왔던 것이다. 하지만 수정된 순수한 버전은 함수를 매번 두 번씩 호출해도 잘 동작한다. (순수 함수는 계산만 하므로 두 번 호출해도 아무 것도 바뀌지 않는다.)
Strict Mode는 상용 환경에서 아무런 영향을 미치지 않아 사용자의 앱 속도에도 영향을 끼치지 않는다. 만약 Strict Mode가 사용하고 싶다면 루트 컴포넌트를 <React.StrictMode>로 감싸면 된다.
콘솔이 두번 출력되서 이상함을 느꼈다면 StrictMode가 적용되어있는지 살펴보자.
지역 변이 : 렌더링하는 동안 '방금' 생성한 변수와 객체를 변경하는것은 괜찮음
위의 예시에서는 컴포넌트가 렌더링하는 동안 기존 변수를 변경하는 것이 문제였다. 하지만 '방금' 생성한 변수와 객체를 변경하는것은 괜찮다.
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 내부에서 동일한 렌더링 중에 생성한 변수의 변경은 TeaGathering 외부의 어떤 코드도 이런 일이 일어났다는 것을 알 수 없기 때문에 괜찮다!
사이드 이펙트를 일으키는 곳
React가 순수성을 중시하긴 하지만, 어딘가에서 무언가 변경이 되어야하는 경우가 있다. 화면 업데이트, 애니메이션, 데이터 변경과 같은 이러한 변경을 사이트 이펙트라하며, 렌더링 중에 일어나는 것이 아닌 "부수적으로" 일어나는 일을 말한다.
React에서 사이드 이펙트는 보통 이벤트 핸들러에 속한다. 이벤트 핸들러는 사용자가 어떤 동작을 수행할 때(버튼을 클릭한다던지) React가 실행하는 함수다. 이벤트 핸들러가 컴포넌트 내부에 정의되어 있긴 하지만 렌더링 중에는 실행되지 않기 때문에 이벤트 핸들러는 순수할 필요가 없다!
다른 모든 옵션을 다 사용했는데도 사이드 이펙트에 적합한 이벤트 핸들러를 찾을 수 없다면, 컴포넌트에서 useEffect 호출을 통해 반환된 JSX에 이벤트 핸들러를 첨부할 수 있다. 이렇게 하면 나중에 렌더링 후 사이드 이펙트가 허용될때 React가 이를 실행하도록 지시한다. 그리고 이 방법은 최후의 수단으로 사용해야한다.
가능하면 렌더링만으로 로직을 표현하고자 노력해보도록하자!
React가 순수성을 중요시하는 이유
- 컴포넌트를 다른 환경(ex. 서버)에서 실행할 수 있다.
동일한 입력에 대해 동일한 결과를 반환하기 때문에 하나의 컴포넌트가 많은 사용자 요청을 처리할 수 있다. - 입력이 변경되지 않는 컴포넌트는 렌더링 건너뛰기(memo)를 통해 성능을 향상시킬 수 있다.
순수 함수는 항상 동일한 결과를 반환하기 때문에 캐싱해도 안전하다. - 깊은 컴포넌트 트리를 렌더링하는 도중에 일부 데이터가 변경되는 React는 오래된 렌더링을 완료하기 위해 시간을 낭비하지 않고 렌더링을 다시 시작할 수 있다.
순수성 덕분에 언제든지 계산을 중단해도 안전하다.
'개발 공부 > React-Next.js' 카테고리의 다른 글
Next.js에서 Devtools가 사라지는 문제 해결 (0) | 2024.08.07 |
---|---|
About Next.js (0) | 2024.08.07 |
[React 학습하기]state: 컴포넌트의 메모리 - React는 어떤 state를 반환할지 어떻게 알 수 있을까?(+ Hooks 이해하기) (0) | 2024.08.07 |
[React 학습하기] 컴포넌트에 props 전달하기 (0) | 2024.08.07 |
[React 학습하기] Quick Start (0) | 2024.08.07 |