[React] UI 표현하기 - 컴포넌트 순수하게 유지하기

Gyuhan Park·2024년 2월 5일
0

공식문서

목록 보기
8/10

[ 요약 ]

  • 컴포넌트는 순수해야함
    • 렌더링전에 존재했던 객체나 변수를 변경 ❌
    • 같은 입력 → 같은 결과
  • React는 작성되는 모든 컴포넌트가 순수 함수일 거라 가정
  • React 컴포넌트가 같은 입력이 주어진다면 반드시 같은 JSX를 반환한다
  • 렌더링 이전에 존재했던 객체나 변수를 변경하지 않는, 컴포넌트를 순수하게 하는 JSX 만 반환해야함
  • 동일한 렌더링동안 생성된 그냥 만든 변수와 객체를 변경하는 것은 문제 ❌
  • 특정 순서로 컴포넌트 렌더링 기대 ❌ → 독립적으로 JSX 연산
  • 이벤트 핸들러가 컴포넌트 내부에 정의되었다 하더라도 렌더링 중에는 실행 ❌ (이벤트 핸들러는 순수할 필요 ❌)
  • 변화 (화면 업데이트, 데이터 변경)를 side effect 라고 하며, 보통 이벤트 핸들러에 포함
  • 웬만하면 렌더링만으로 로직 표현하고 useEffect는 마지막 수단으로 사용

[ 학습 내용 ]

  • 순수성이란 무엇인지 그리고 어떻게 버그를 피하도록 도울 건지 배웁니다.
  • 렌더 단계에서 변화를 유지하면서 컴포넌트를 순수하게 유지할 것인지 배웁니다.
  • 엄격 모드를 어떻게 활용해서 컴포넌트에 실수를 발견할 수 있는지 배웁니다.

📘 순수성: 공식으로서의 컴포넌트

컴퓨터 과학에서 순수함수의 특징 (특히 함수형 프로그래밍의 세계)

  • 자신의 일에 집중
    • 함수가 호출되기 전에 존재했던 어떤 객체나 변수는 변경하지 않습니다.
  • 같은 입력, 같은 출력
    • 같은 입력이 주어졌다면 순수함수는 같은 결과를 반환해야 합니다.

React는 이러한 컨셉을 기반으로 설계되었음
React는 작성되는 모든 컴포넌트가 순수 함수일 거라 가정
이러한 가정은 작성되는 React 컴포넌트가 같은 입력이 주어진다면 반드시 같은 JSX를 반환한다 는 것을 의미

ex) drinkers에 2를 전달하면 Recipe는 항상 2 cups of water 를 반환

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

📘 사이드 이펙트: 의도하지 않은 결과

“React의 렌더링 과정은 항상 순수해야한다.”

렌더링 이전에 존재했던 객체나 변수를 변경하지 않는, 컴포넌트를 순수하게 하는 JSX 만 반환해야함

이러한 규칙을 위반하는 컴포넌트 예시 코드

let guest = 0;

function Cup() {
  // 나쁜 지점: 이미 존재했던 변수를 변경하고 있다!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

컴포넌트 바깥에 선언된 guest 변수를 수정
→ 컴포넌트가 여러번 호출되면 다른 JSX를 생성한다는 것을 의미
다른 컴포넌트가 guest를 읽었다면 언제 렌더링되었는지에 따라 그 컴포넌트 또한 다른 JSX를 생성함
→ 예측 ❌, 의도한 결과 ❌

guest 변수 대신 props로 넘겨 이 컴포넌트를 고칠 수 있음

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

위에서 JSX가 반환되는 것은 오직 guest 에 의존하기 떄문에 순수한 컴포넌트
특정 순서로 컴포넌트 렌더링 기대 ❌ → 독립적 으로 JSX 연산
각 컴포넌트는 렌더링 중에 다른 컴포넌트를 의존하지 말고 자체적으로 JSX를 연산해야함

🚨 엄격 모드로 순수하지 않은 연산을 감지

React가 렌더링하면서 읽을 수 있는 3가지 종류의 입력 요소 : props, state, context
사용자의 입력에 따라 무언가를 변경 → setState
컴포넌트가 렌더링되는 동안에는 기존 변수나 개체를 변경하면 안됨
strict mode : 개발 중에 각 컴포넌트의 함수를 두 번 호출

순수 함수는 두 번 호출되더라도 정상적으로 동작 (같은 입력 → 같은 출력)
production에 영향을 주지 않기 때문에 사용자의 앱 속도가 느려지지 않음

📘 지역 변형: 컴포넌트의 작은 비밀

[ 문제 상황 ]
렌더링하는 동안 컴포넌트가 기존 변수를 변경했다는 것

순수 함수 → 함수 스코프 밖에 변수나 호출 전에 생성된 객체를 변경하지 않음
But, 렌더링하는 동안 그냥 만든 변수와 객체를 변경하는 것 은 문제 ❌

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

TeaGathering 컴포넌트 안에 동일한 렌더링 중에 생성되었기 때문에 ✅ (컴포넌트 바깥에 선언 시 문제)
지역 변형 (TeaGathering 외부에서는 모름)

📘 부작용을 일으킬 수 있는 지점

함수형 프로그래밍은 순수성 에 크게 의존 → 어떻게 바꿈?
변화 (화면 업데이트, 애니메이션 시작, 데이터 변경) : side effect
side effect는 보통 이벤트 핸들러에 포함
이벤트 핸들러가 컴포넌트 내부에 정의되었다 하더라도 렌더링 중에는 실행 ❌
→ 그래서 이벤트 핸들러는 순수할 필요
적합한 이벤트 핸들러를 찾을 수 없는 경우, useEffect 를 호출하여 반환된 JSX에 해당 이벤트 핸들러 연결
side effect가 허용될 때 렌더링 후 나중에 실행하도록 지시 → useEffect는 마지막 수단으로 사용

=> 웬만하면 렌더링만으로 로직 표현하기

🚨 리액트는 왜 순수함을 신경쓸까요?

  • 컴포넌트는 다른 환경에서도 실행될 수 있음
    • 동일한 입력에 대해 동일한 결과를 반환하기 때문에 하나의 컴포넌트는 많은 사용자 요청 처리
  • 입력이 변경되지 않은 컴포넌트의 렌더링을 건너뛰어 성능 향상
    • 순수 함수는 항상 동일한 결과를 반환하므로 캐시하기에 안전
  • 깊은 컴포넌트 트리를 렌더링하는 도중에 일부 데이터가 변경되는 경우 즉시 리렌더링
    • React는 오래된 렌더링을 완료하는데 시간을 낭비하지 않고 렌더링을 다시 시작 가능
    • 순수함은 언제든지 연산을 중단하는 것을 안전하게 함

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

profile
단단한 프론트엔드 개발자가 되고 싶은

0개의 댓글