고양이와 코딩

[React] - 컴포넌트 순수성 유지 본문

react

[React] - 컴포넌트 순수성 유지

ovovvvvv 2024. 2. 28. 02:06
728x90

https://react-ko.dev/learn/keeping-components-pure

 

컴포넌트 순수성 유지 – React

The library for web and native user interfaces

react-ko.dev

 

리액트 공식문서 + 함수형 코딩스터디를 동시에 하며 도움이 되어서 포스팅 합니다 !

 

컴포넌트를 엄격하게 순수 함수로 작성하기 위한 규칙

  • 호출되기 전에 존재했던 객체나 변수를 변경하지 않습니다
  • 동일한 입력이 주어지면 항상 동일한 결과를 반환해야 합니다.

 

→ React는 이 개념을 중심으로 설계되었기에, 우리가 작성하는 모든 컴포넌트가 순수 함수라고 가정한다. 

 

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

위 컴포넌트는 drinkers를 props로 받아(직접적인 접근x) 리스트에 전달하고 같은 입력값에 대해 항상 같은 결과를 출력합니다.

 

let Drinkers;

function Recipe() {
  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() {
  Drinkers = 2;
  return (
    <section>
      <h1>Spiced Chai Recipe</h1>
      <h2>For two</h2>
      <Recipe />
      {Drinkers = 4}
      <h2>For a gathering</h2>
      <Recipe />
    </section>
  );
}

하지만 Drinkers가 전역 변수로 선언되었고, 이를 사용한다면 계산이 수행됨에 따라 변수의 값이 달라지므로 결과가 예측할 수 없게 됩니다. 이는 사이드이펙트(의도하지 않은 결과)를 발생시킵니다

 

 

React에서 렌더링 하는 동안 읽을 수 있는 입력 세가지

  • props
  • state
  • context

순수성 유지를 위해 이 세 입력은 항상 `읽기 전용`으로 취급해야 합니다

사용자 입력에 대한 응답으로 무언가 변경시키려면 변수를 변경하는 대신 state를 설정해야 합니다

(컴포넌트가 렌더링 되는 동안 기존 변수나 객체를 변경해서는 안됩니다 !)

 

렌더링 하는 동안, `방금` 생성한 변수와 객체를 변경하는 것은 괜찮습니다

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는 함수 내부에 존재하기 때문에 변경해도 컴포넌트가 순수성을 잃지 않습니다 (지역변수는 괜찮다!)

 

사이드 이펙트를 일으킬 수 있는 곳

  • 화면 업데이트
  • 애니메이션 시작
  • 데이터 변경
  • 이메일 보내기  
  • .......

프로그래밍에서 사이드 이펙트 없이 뭔가를 만드는건 불가합니다 !

(`쏙쏙 들어오는 함수형 코딩` 에서는 이메일을 보내는 시스템을 개발하는데 이메일을 보내지 않을 수는 없다고 말합니다)

 

React에서는 보통 이런 사이드 이펙트가 이벤트 핸들러에 속합니다! (예: 버튼 클릭)

이런 이벤트 핸들러들은 컴포넌트 내부에 정의되지만, 실제로는 해당 컴포넌트가 렌더링 될 때 실행되지 않습니다.

대신, 사용자가 이벤트를 발생시키는 동작을 할 때만 실행됩니다.

 

컴포넌트가 렌더링 될 때 마다 실행되는게 아니기 때문에 이벤트 핸들러는 순수 함수일 필요가 없습니다.

 

그렇다면 useEffect 사용은 지양해야 할까?

위 내용을 읽고, useEffect는 렌더링이 될 때마다 실행되기 때문에 많은 부수 효과를 발생시키지 않나? 라는 생각이 들었습니다.

 

실제로 문서에서는 모든 옵션을 고려하고도 사이드 이펙트에 적합한 이벤트 핸들러를 찾을 수 없다면 useEffect를 사용하라고 말합니다

하지만 useEffect를 아예 사용하지 않을 수 없으므로, 최대한 다양한 경우의 수를 생각한 후 최후의 수단으로 사용하길 권장합니다!

 

요약

  • 컴포넌트는 순수해야 합니다:
    • 자신의 일에만 신경씁니다. 렌더링 전에 존재했던 객체나 변수를 변경하지 않아야 합니다.
    • 동일한 입력, 동일한 출력. 동일한 입력이 주어지면 컴포넌트는 항상 동일한 JSX를 반환해야 합니다.
  • 렌더링은 언제든지 발생할 수 있으므로, 컴포넌트는 서로의 렌더링 순서에 의존해서는 안 됩니다.
  • 컴포넌트가 렌더링에 사용하는 어떠한 입력값도 변이해서는 안 됩니다. 여기에는 props, state 및 context가 포함됩니다. 화면을 업데이트하려면 기존 객체를 변이하는 대신 “set” state를 사용하세요.
  • 컴포넌트의 로직을 반환하는 JSX 안에 표현하기 위해 노력하세요. “무언가를 변경”해야 할 때는 보통 이벤트 핸들러에서 이 작업을 수행하고자 할 것입니다. 최후의 수단으로 useEffect를 사용할 수도 있습니다.
  • 순수 함수를 작성하는 데는 약간의 연습이 필요하지만, React 패러다임의 힘을 발휘할 수 있습니다.