본문 바로가기
React/Documentation

리액트 공식 문서 번역-08. 컴포넌트 순수성 유지하기

by 똘이토스 2023. 7. 10.

일부 JavaScript 함수는 순수 함수(pure function)입니다. 순수 함수는 계산만 수행하고 그 이상의 작업을 수행하지 않습니다. 컴포넌트를 순수 함수로 엄격하게 작성함으로써 코드베이스가 커져도 이해하기 어려운 버그와 예측할 수 없는 동작을 피할 수 있습니다. 그러나 이러한 이점을 얻으려면 몇 가지 규칙을 준수해야 합니다.

 

Purity: 컴포넌트를 공식(formula)으로 만들기

컴퓨터 과학에서 (특히 함수형 프로그래밍의 세계에서), 순수 함수는 다음과 같은 특성을 갖는 함수입니다

  • 자신만의 일을 처리합니다. 호출되기 전에 존재했던 객체나 변수를 변경하지 않습니다.
  • 동일한 입력에 대해 동일한 출력을 반환합니다. 같은 입력이 주어지면, 순수 함수는 항상 동일한 결과를 반환해야 합니다.

순수 함수의 예로 수학에서의 공식을 들 수 있습니다. 다음과 같은 수학 공식을 고려해보세요: y = 2x.

만약 x = 2이면 y = 4입니다. 항상 그렇습니다.

만약 x = 3이면 y = 6입니다. 항상 그렇습니다.

만약 x = 3일 때, y는 언제나 시간대나 주식 시장의 상태에 따라 9 또는 -1 또는 2.5가 될 수 없습니다.

만약 y = 2x이고 x = 3이면, y는 항상 6이 됩니다.

우리가 이를 JavaScript 함수로 만들면 다음과 같을 것입니다

 

function double(number) {
  return 2 * number;
}

 

위 예제에서 double은 순수 함수입니다. 3을 전달하면 항상 6을 반환합니다.

 

React는 이 개념을 기반으로 설계되었습니다. React는 모든 컴포넌트가 순수 함수라고 가정합니다. 이는 React 컴포넌트가 항상 동일한 입력을 받았을 때 동일한 JSX를 반환해야 한다는 것을 의미합니다.

https://codesandbox.io/s/88lm7b?file=/App.js&utm_medium=sandpack

 

happy-smoke-88lm7b - CodeSandbox

happy-smoke-88lm7b using react, react-dom, react-scripts

codesandbox.io

 

drinkers={2}를 Recipe에 전달하면 항상 물 2컵이 포함 된 JSX가 반환됩니다.

 

drinkers={4}를 전달하면 항상 물 4컵이 포함 된 JSX가 반환됩니다.

 

수학 공식과 마찬가지로.

 

컴포넌트를 레시피처럼 생각할 수 있습니다. 그들을 따르고 요리 과정 중 새로운 재료를 도입하지 않으면 매번 같은 요리를 얻게됩니다. 그 "요리"는 컴포넌트가 렌더링하기 위해 React에 제공하는 JSX입니다.

 

Side Effects: (의도하지 않은) 부작용

React의 렌더링 과정은 항상 순수해야합니다. 컴포넌트는 JSX 만 반환하고 렌더링 이전에 존재하는 모든 객체나 변수를 변경해서는 안됩니다. 그렇게하면 순수하지 않게됩니다!

 

다음은 이 규칙을 어긴 컴포넌트입니다

 

https://codesandbox.io/s/29vywb?file=/App.js&utm_medium=sandpack

 

long-bird-29vywb - CodeSandbox

long-bird-29vywb using react, react-dom, react-scripts

codesandbox.io

 

이 구성 요소는 외부에서 선언된 guest 변수를 읽고 쓰고 있습니다. 이는 이 구성 요소를 여러 번 호출하면 다른 JSX가 생성된다는 것을 의미합니다! 더구나, 다른 구성 요소가 guest를 읽으면 렌더링된 시점에 따라 다른 JSX가 생성됩니다. 이는 예측할 수 없는 동작입니다.

 

우리의 공식인 y = 2x로 돌아가면, 이제 x = 2라고 해도 y = 4를 믿을 수 없습니다. 우리의 테스트가 실패할 수 있고, 사용자들은 당혹스러워할 것이며, 비행기들은 하늘에서 떨어질 수 있습니다. 이러한 혼란스러운 버그가 발생할 수 있기 때문입니다!

 

이 구성 요소를 수정하여 guest를 프롭으로 전달함으로써 이 문제를 해결할 수 있습니다

 

https://codesandbox.io/s/e0bvgk?file=/App.js&utm_medium=sandpack

 

interesting-wilson-e0bvgk - CodeSandbox

interesting-wilson-e0bvgk using react, react-dom, react-scripts

codesandbox.io

 

이제 당신의 구성 요소는 guest 프롭에만 의존하는 JSX만 반환하기 때문에 순수합니다.

 

일반적으로 구성 요소가 어떤 특정한 순서로 렌더링될 것이라고 기대해서는 안됩니다. y = 2x를 y = 5x보다 먼저 호출하든 나중에 호출하든 상관없습니다. 두 공식은 서로 독립적으로 해결됩니다. 마찬가지로 각 구성 요소는 스스로만 "생각"하고, 렌더링 중에 다른 구성 요소와 조정하거나 의존하려고 시도해서는 안됩니다. 렌더링은 시험과 같습니다. 각 구성 요소는 자신의 JSX를 계산해야 합니다!

 

StrictMode를 사용하여 비순수 계산 감지하기

더보기

React에서는 렌더링하는 동안 읽을 수 있는 세 가지 종류의 입력이 있습니다. 프롭스(props), 스테이트(state)컨텍스트(context)입니다. 항상 이러한 입력을 읽기 전용으로 다루어야 합니다.

 

사용자 입력에 응답하여 무언가를 변경하려면 변수에 쓰는 대신 스테이트(state)를 설정해야 합니다. 구성 요소가 렌더링되는 동안 기존 변수나 객체를 변경해서는 안 됩니다.

 

React는 개발 중에 각 구성 요소의 함수를 두 번 호출하여 "Strict Mode"를 제공합니다. 구성 요소 함수를 두 번 호출함으로써 Strict Mode는 이러한 규칙을 위반하는 구성 요소를 찾아줍니다.

 

원래 예제에서 "Guest #2", "Guest #4", "Guest #6"이 표시된 것을 볼 수 있습니다. 이는 원래 함수가 비순수했기 때문에 두 번 호출하면 제대로 작동하지 않았기 때문입니다. 그러나 수정된 순수 버전은 함수를 매번 두 번 호출해도 작동합니다. 순수 함수는 계산만 수행하기 때문에 두 번 호출해도 결과가 변경되지 않습니다. double(2)를 두 번 호출하거나 y = 2x를 두 번 푸는 것도 반환값이 변경되지 않는 것과 같습니다. 같은 입력, 같은 출력입니다. 항상 그렇습니다.

 

Strict Mode는 프로덕션에서는 영향을 미치지 않기 때문에 사용자 앱 속도를 늦추지 않습니다. Strict Mode를 활성화하려면 루트 구성 요소를 <React.StrictMode>로 래핑할 수 있습니다. 일부 프레임워크는 이 기능을 기본으로 제공합니다.

 

지역적인 변이(Local mutation): 구성 요소의 작은 비밀

위의 예제에서 문제는 구성 요소가 렌더링하는 동안 기존 변수를 변경했다는 것입니다. 이것은 때로 "변이"라고 부르기도 하며 약간 무서워 보입니다. 순수 함수는 호출 전에 생성된 함수의 범위 밖의 변수나 객체를 변이시키지 않습니다. 이러한 변이는 함수를 불순하게 만듭니다!

 

그러나 렌더링하는 동안 만들어진 변수나 객체는 변경해도 괜찮습니다. 이 예제에서는 [] 배열을 만들고, 이를 cups 변수에 할당한 다음, 12개의 컵을 push하는 것입니다.

 

https://codesandbox.io/s/26q1e4?file=/App.js&utm_medium=sandpack

 

boring-fast-26q1e4 - CodeSandbox

boring-fast-26q1e4 using react, react-dom, react-scripts

codesandbox.io

 

만약 cups 변수나 [] 배열이 TeaGathering 함수 외부에서 생성된 것이라면, 이는 큰 문제가 될 것입니다! 이러한 배열에 항목을 push함으로써 기존 객체를 변경하는 것입니다.

 

그러나 이 예에서는 TeaGathering 함수 내에서 같은 렌더링 중에 이러한 변수와 배열이 생성되었습니다. TeaGathering 외부의 코드는 이러한 일이 발생했다는 것을 전혀 알 수 없습니다. 이것을 "지역적인 변이(local mutation)"라고 합니다. 구성 요소의 작은 비밀처럼 보입니다.

 

부수 효과를 발생시킬 수 있는 곳

함수형 프로그래밍은 순수성에 매우 의존하지만, 어느 시점에는 어딘가에서 뭔가가 변해야 합니다. 프로그래밍의 핵심이기도 합니다! 이러한 변경사항 - 화면 업데이트, 애니메이션 시작, 데이터 변경 등 - 을 부수 효과(side effects)라고 합니다. 이러한 것들은 렌더링 중이 아니라 "옆에서" 발생하는 것입니다.

 

React에서 부수 효과는 일반적으로 이벤트 핸들러 내부에 속합니다. 이벤트 핸들러는 어떤 작업을 수행할 때 React가 실행하는 함수입니다. 예를 들어, 버튼을 클릭할 때 이벤트 핸들러가 실행됩니다. 이벤트 핸들러는 컴포넌트 내에서 정의되지만 렌더링 중에 실행되지 않습니다! 그래서 이벤트 핸들러는 순수하지 않아도 됩니다.

 

부수 효과를 위한 올바른 이벤트 핸들러를 찾을 수 없을 때, 여전히 컴포넌트의 반환된 JSX에 useEffect 호출을 사용하여 부수 효과를 추가할 수 있습니다. 이렇게 하면 React가 렌더링 후에 이를 실행하도록 알려줍니다. 그러나 이 방법은 마지막 수단이어야 합니다.

 

가능한 경우 렌더링으로만 논리를 표현하려고 노력해보세요. 이것이 얼마나 많은 일을 할 수 있는지 놀랄 것입니다!

 

React가 왜 순수성(purity)에 관심을 가지는 걸까요?

더보기

순수 함수를 작성하는 것은 습관과 규율이 필요합니다. 그러나 이렇게 하면 놀라운 기회가 열립니다:

  • 여러분의 컴포넌트는 서버와 같은 다른 환경에서도 실행될 수 있습니다! 순수 함수는 동일한 입력에 대해 항상 동일한 결과를 반환하기 때문에 하나의 컴포넌트가 여러 사용자 요청을 처리할 수 있습니다.
  • 입력이 변경되지 않은 컴포넌트를 렌더링하지 않고 성능을 향상시킬 수 있습니다. 순수 함수는 항상 동일한 결과를 반환하기 때문에 캐시해도 안전합니다.
  • 중첩된 컴포넌트 트리를 렌더링하는 도중 데이터가 변경된 경우, 순수성을 유지하면 언제든지 계산을 중지할 수 있으므로 React는 오래된 렌더링을 마치는 데 낭비되는 시간 없이 렌더링을 다시 시작할 수 있습니다.
  • 우리가 만들고 있는 새로운 React 기능은 모두 순수성을 활용합니다. 데이터 가져오기부터 애니메이션, 성능까지 모든 것을 순수 컴포넌트로 만들면 React 패러다임의 장점을 최대한 활용할 수 있습니다.

 

요약

  • 컴포넌트는 순수(pure)해야 합니다. 즉,
    • 컴포넌트는 자신의 일만 하도록 해야 합니다. 렌더링 이전에 존재하던 객체나 변수를 변경해서는 안 됩니다.
    • 같은 입력에 대해 항상 같은 JSX를 반환해야 합니다.
  • 렌더링은 언제든 일어날 수 있으므로, 컴포넌트는 서로의 렌더링 순서에 의존해서는 안 됩니다.
  • 렌더링에 사용되는 입력값(프롭, 상태, 컨텍스트 등)은 변경하면 안 됩니다. 객체를 직접 수정하는 대신 상태를 "설정"하여 화면을 업데이트해야 합니다.
  • 컴포넌트의 로직은 반환하는 JSX에서 표현하는 것이 좋습니다. "무언가를 바꾸어야" 하는 경우 대부분 이벤트 핸들러에서 처리하게 될 것입니다. 마지막 수단으로 useEffect를 사용할 수 있습니다.
  • 순수 함수를 작성하는 것은 조금의 연습이 필요하지만, React의 패러다임을 잠재우는 데 큰 도움이 됩니다.

 

2023.07.10 - [React/Documentation] - 리액트 공식 문서 번역-09. 이벤트에 대한 응답

 

리액트 공식 문서 번역-09. 이벤트에 대한 응답

React는 JSX에 이벤트 핸들러를 추가할 수 있도록 합니다. 이벤트 핸들러는 클릭, 호버링, 폼 입력에 대한 포커싱 등과 같은 상호작용에 응답하여 트리거되는 사용자 정의 함수입니다. 이벤트 핸들

ddor2.tistory.com