[React] 컴포넌트 순수성(pure) 유지하기

summereuna🐥·2023년 3월 10일
0

React JS

목록 보기
26/69

📌 이 포스팅은 React Docs의 문서를 번역한 것입니다.

일부 자바스크립트 함수는 순수하다. 순수 함수는 계산만 수행하고 그 이상은 수행하지 않는다. 컴포넌트를 순수 함수로만 엄격하게 작성하면, 코드 기반이 증가함에 따라 전체 클래스의 당혹스러운 버그와 예측할 수 없는 동작을 피할 수 있다. 그러나 이러한 이점을 얻기 위해서는 몇 가지 규칙을 따라야 한다.

📚 배울 내용

  • 순수함이란 무엇이며, 그것이 버그를 피하는 데 어떻게 도움이 되는지
  • 렌더 단계에서 변경사항을 제외하여 컴포넌트를 순수하게 유지하는 방법
  • 엄격 모드(Strict Mode)를 사용하여 컴포넌트의 오류를 찾는 방법

💬 Purity: Components as formulas

순수함이란: (수학)공식 같은 컴포넌트

순수 함수(pure function)는 컴퓨터 과학(특히 함수형 프로그래밍)에서 다음과 같은 특성을 갖는 함수이다:

  • 자기 일에 신경 쓴다. 순수 함수는 호출되기 전에 존재했던 개체나 변수는 변경하지 않는다.
  • 같은 입력, 같은 출력. 동일한 입력이 주어지면, 순수 함수는 항상 동일한 결과를 반환해야 한다.

여러분은 이미 순수 함수의 한 가지 예, 즉 수학의 공식에 익숙할 수도 있다.
이 수학 공식을 생각해 보자: y = 2x
x=2이면 항상 y=4.
x=3이면 항상 y=6.
x=3이면 y는 때때로 시간이나 주식 시장의 상태에 따라 9 또는 1 또는 2.5가 되지 않는다.
y=2x이고 x=3이면 y는 항상 6이 된다.

이것을 자바스크립트 함수로 만들면 다음과 같이 보일 것이다.

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

위의 예제에서 double순수 함수이다. 3을 넣으면 항상 6을 돌려준다.

React는 이 개념을 중심으로 설계되었다. React는 사용자가 작성하는 모든 컴포넌트가 순수 함수라고 가정한다. 즉, 당신이 작성한 React 컴포넌트는 항상 동일한 입력이 주어지면 동일한 JSX를 반환해야 한다:

App.js

function Recipe({ drinkers }) {
  return (
    <ol>    
      <li>Boil {drinkers} cups of water.</li>
      <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li>
      <li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li>
    </ol>
  );
}
//
export default function App() {
  return (
    <section>
      <h1>Spiced Chai Recipe</h1>
      <h2>For two</h2>
      <Recipe drinkers={2} />
      <h2>For a gathering</h2>
      <Recipe drinkers={4} />
    </section>
  );
}

Spiced Chai Recipe

For two

1. Boil 2 cups of water.

2. Add 2 spoons of tea and 1 spoons of spice.

3. Add 1 cups of milk to boil and sugar to taste.

For a gathering

1. Boil 4 cups of water.

2. Add 4 spoons of tea and 2 spoons of spice.

3. Add 2 cups of milk to boil and sugar to taste.

drinkers={2}Recipe에 전달하면 2 cups of water가 포함된 JSX가 항상 반환된다.
drinkers={4}를 보내면 4 cups of water가 포함된 JSX가 항상 반환된다.

수학 공식처럼 말이다.

여러분은 여러분의 컴포넌트를 조리법(Recipe)으로 생각할 수 있다: 여러분이 레시피를 따르고 요리 과정 동안 새로운 재료들을 소개하지 않는다면, 여러분은 매번 같은 요리를 얻게 된다. 이 "접시"는 컴포넌트가 React에서 렌더링하는 역할을 하는 JSX이다.


💬 Side Effects: (un)intended consequences

부작용: 의도하지 않은 결과

리액트의 렌더링 프로세스는 항상 순수해야 한다. 컴포넌트는 JSX만 반환해야 하며, 렌더링 전에 존재했던 어떤 객체나 변수도 변경해서는 안 된다!

다음은 이 규칙을 어기는 컴포넌트이다:

App.js

let guest = 0;
//
function Cup() {
  // ❗️Bad: 기존 변수를 변경한다!
  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가 생성된다! 즉, 예측할 수 없게 되어 버린다.

수학 공식 y=2x로 돌아가면, 이제 x=2일지라도, y=4라는 것을 믿을 수 없게 된다. 우리 테스트가 실패할 수도 있고, 우리 사용자들은 당황할 수도 있고, 비행기가 하늘에서 떨어질 수도 있다. 이것이 어떻게 버그를 혼란스럽게 하는지 알 수 있다!

대신 guestprop으로 전달하여 이 컴포넌트를 수정할 수 있다.

App.js

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} />
    </>
  );
}

Tea cup for guest #1

Tea cup for guest #2

Tea cup for guest #3

이제 컴포넌트는 순수하다. JSX가 반환하는 것은 guest prop에만 의존하기 때문이다.

일반적으로 컴포넌트가 특정 순서로 렌더링될 것으로 예상해서는 안 된다. y=5x 전후에 y=2x를 불러도 상관 없다. 두 공식은 서로 독립적으로 해결된다. 마찬가지로, 각 구성 요소는 "스스로 생각"해야만 하며 렌더링하는 동안 다른 컴포넌트와 조정하거나 다른 컴포넌트를 의존하려고 해서는 안 된다. 렌더링은 학교에서 시험 보는 것과 같다. 각 컴포넌트는 자체적으로 JSX를 계산해야 한다!


💬 Local mutation: Your component’s little secret

국소 변형: 컴포넌트의 작은 비밀

위의 예에서는 렌더링하는 동안 컴포넌트가 기존 변수를 변경한 것이 문제였다. 이것은 종종 조금 더 무섭게 들리게 하기 위해 "mutation(변형)"이라고 불립니다. 순수 함수는 함수의 범위 밖에 있는 변수나 호출 전에 생성된 개체를 변형시키지 않는다. 이는 변수를 불순하게 만든다!

그러나 렌더링하는 동안 방금 만든 변수와 객체를 변경하는 것은 완전히 괜찮다. 이 예제에서는 [] 배열을 만들고, 그것을 cups 변수에 할당한 다음, 12개의 컵을 그 안에 push해 보자:

App.js

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;
}

Tea cup for guest #1

Tea cup for guest #2

Tea cup for guest #3

Tea cup for guest #4

Tea cup for guest #5

Tea cup for guest #6

Tea cup for guest #7

Tea cup for guest #8

Tea cup for guest #9

Tea cup for guest #10

Tea cup for guest #11

Tea cup for guest #12

cups 변수 또는 [] 배열이 TeaGathering 기능 외부에서 생성된 경우, 이것은 큰 문제가 될 수 있다! 항목을 해당 배열에 밀어넣어 기존 객체를 변경할 수 있기 때문이다.

그러나 같은 렌더 중에,TeaGathering 내부에서 생성했기 때문에 괜찮다. TeaGathering 밖의 어떤 코드도 이런 일이 일어났다는 것을 알지 못할 것이다. 이를 "Local mutation(국소 변형)"이라고 한다. 이는 컴포넌트의 작은 비밀이다.


💬 Where you can cause side effects

사이드 이펙트(부작용)를 일으킬 수 있는 곳

함수형 프로그래밍은 순수성에 크게 의존하지만, 어느 시점에서는 어딘가에서 무언가가 바뀌어야 한다. 그것이 프로그래밍의 요점이다! 화면 업데이트, 애니메이션 시작, 데이터 변경과 같은 이러한 변경을 side effects(사이드 이펙트: 부작용)라고 한다. 그것들은 렌더링 중에 발생하는 것이 아니라 "on the side(측면에서)" 발생하는 것들이다.

React에서 사이드 이펙트(부작용)는 일반적으로 이벤트 핸들러 내부에 있다. 이벤트 핸들러는 사용자가 일부 액션을 수행할 때 실행되는 기능이다. 예를 들어, 버튼을 클릭할 때 실행된다. 이벤트 핸들러는 컴포넌트 내부에 정의되어 있지만 렌더링 중에 실행되지 않는다! 따라서 이벤트 핸들러는 순수할 필요가 없다.

다른 모든 옵션을 다 써 버렸고 사이드 이펙트에 적합한 이벤트 처리기를 찾을 수 없는 경우, 컴포넌트에서 useEffect 호출을 사용하여 반환 된 JSX에 연결할 수 있다. 이것은 React에게 나중에, 렌더링 후, 사이드 이펙트가 허용될 때 실행하도록 지시한다. 그러나 이러한 접근 방식은 최후의 수단이 되어야 한다.

가능하면 렌더링만으로 로직(논리)을 표현해 보자.


📝 요약


  • 컴포넌트는 순수해야 한다:

    • 컴포넌트는 자신의 일에만 신경을 쓴다. 렌더링하기 전에 존재했던 어떤 물체나 변수도 바꾸어서는 안 된다.
    • 동일한 입력, 동일한 출력. 동일한 입력이 주어지면 컴포넌트는 항상 동일한 JSX를 반환해야 한다.
  • 렌더링은 언제든지 발생할 수 있으므로 컴포넌트는 서로의 렌더링 순서에 의존해서는 안된다.

  • 컴포넌트가 렌더링에 사용하는 입력을 변경해서는 안 된다. 여기에는 props, state, context가 포함된다. 화면을 업데이트하려면 기존 객체를 변경하는 대신 "set"state를 변경하자.

  • 반환하는 JSX에서 컴포넌트의 로직(논리)를 표현하기 위해 노력해라. "어떤 것을 변경"해야 할 경우 일반적으로 이벤트 핸들러에서 변경할 수 있다. 마지막 수단으로 useEffect를 사용할 수 있다.

  • 순수한 함수를 작성하는 데는 약간의 연습이 필요하지만 React의 패러다임을 열어준다.


📚 출처

React Docs | Learn React > Describing the UI > Keeping Components Pure

profile
Always have hope🍀 & constant passion🔥

0개의 댓글