[React] 컴포넌트의 순수성

Sheryl Yun·2023년 11월 9일
0
post-thumbnail

📌 순수 함수란?

  • 호출되기 전에 존재했던 기존의 객체나 변수를 변경하지 않는 함수
  • 동일한 입력이 주어지면 항상 동일한 결과를 반환하는 함수
  • 순수 함수는 수학 공식, 요리 레시피와 같다.
    • 같은 숫자 또는 같은 재료가 들어가면 같은 연산 결과, 같은 요리(= JSX) 완성
// 예: 3을 전달하면 언제나 6을 반환
function double(number) {
  return 2 * number;
}
  • React는 모든 컴포넌트가 순수 함수라고 가정
    • 즉, 동일한 입력이 주어졌을 때 항상 동일한 JSX를 반환해야

📌 Side Effect에 관해

  • 컴포넌트는 오직 JSX만을 반환해야 함
  • JSX 계산 시 렌더링 전에 존재했던 기존의 객체나 변수를 변경해선 안 됨

예시

순수하지 않은 컴포넌트

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 />
    </>
  );
}
  • 외부에서 선언된 guest 변수를 읽고 변경하는 컴포넌트
    • 호출할 때마다(= 렌더링 시점에 따라) JSX가 다르게 생성됨
    • 다른 컴포넌트가 guest를 읽거나 변경할 가능성 배제할 수 없음

컴포넌트가 순수하지 않으면 생기는 부작용

  • 테스트 코드 결과를 믿을 수 없음
  • 사용자가 사용하다가 어떤 일이 발생할 지 개발자가 예측할 수 없음

순수한 컴포넌트

  • 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} />
    </>
  );
}
  • 이제 JSX의 변경은 guest prop에만 의존 (= 순수, 예측 가능)
  • 일반적으로 컴포넌트는 호출 시점에 상관 없이 렌더링 결과가 일정해야 함
    • 마치 수학 공식처럼 각각의 연산 과정이 서로에게 영향을 주지 않아야 함

🌊 Deep Dive: StrictMode

  • React 렌더링에 필요한 값: props, state, context
    • 항상 읽기 전용(readonly)이어야 함
  • 무언가를 변경하려면 항상 state를 설정하고 setState로 변경
  • 컴포넌트가 렌더링되는 동안에는 기존 변수나 객체를 절대 변경해선 안 됨

React가 제공하는 Strict Mode

  • 개발 환경에서 각 컴포넌트의 함수를 두 번씩 호출하게 하는 모드
  • 첫 호출과 두 번째 호출의 결과를 비교하여 순수하지 않은 함수(컴포넌트)를 찾아냄
  • guest 예제에서 원래 함수는 불완전했기 때문에 두 번 호출하면 함수가 손상되었음
  • 하지만 수정된 순수한 버전은 함수를 두 번씩 호출해도 잘 동작함
  • 순수 함수는 같은 입력에 같은 결과만을 반환하므로 두 번씩 호출해도 아무 것도 바뀌지 않음

🙋‍♀️ Good to know

  • Strict Mode는 사용자가 실제 사용하는 배포 환경에서는 동작하지 않음
    • 사용자가 사용하는 속도에 아무런 영향 x
  • Strict Mode 설정: root 컴포넌트를 <React.StrictMode>로 감싸기

📌 컴포넌트의 지역 변이 (local mutation)

  • 변이(mutation)
    • 렌더링하는 동안 기존 변수를 변경하는 동작
  • 순수 함수는 외부 변수나 호출 전에 생성된 객체를 '변이'하지 않음

그러나 '렌더링 동안 생성된' 변수와 객체를 변경하는 것은 가능
(스코프의 차이)

예시

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} />); // 원본을 변경하여 Cup 컴포넌트 추가
  }
  
  return cups; // 반환
}
  • cups 변수의 빈 배열이 TeaGathering 함수의 외부에서 생성되었다면 다른 컴포넌트에서도 변경할 수 있고 기존에 생성된 외부 변수를 변경하게 되기 때문에 순수하지 않음
  • 하지만 위 코드는 TeaGathering 내부에서 (동일한 렌더링 중에) 생성했기 때문에 괜찮음 (순수)
    • 이렇게 하면 TeaGathering 바깥의 어떤 컴포넌트도 TeaGathering 내부의 일을 알 수 없음 => 지역 변이(local mutation)

🙋‍♀️ 대표적인 사이드 이펙트: 이벤트 핸들링

  • 화면 업데이트, 애니메이션 등
  • 렌더링 중에 일어나는 것이 아니라 (렌더링 후에) “부수적으로” 일어나는 일 => 사이드 이펙트
  • React에서 사이드 이펙트는 보통 이벤트 핸들러에 속함
  • 이벤트 핸들러는 사용자가 특정 동작을 수행할 때 실행하는 함수
  • 컴포넌트 내부에 정의되어 있긴 하지만 렌더링 중에는 실행되지 않음
    => 이벤트 핸들러는 순수할 필요가 없음
  • useEffect를 통해 반환된 JSX에 이벤트 핸들러를 첨부할 수 있지만 이 방법은 되도록 안 쓰는 게 좋음 (최후의 수단)
  • 가능하면 렌더링만으로 로직을 표현하고자 노력해 보자

🌊 Deep Dive: React는 왜 순수성을 중시할까?

순수 함수의 장점

  • 컴포넌트를 다른 환경(예: 서버)에서 실행할 수 있음
  • 하나의 컴포넌트가 많은 사용자 요청을 처리할 수 있음
  • '렌더링 건너뛰기(캐싱)'를 통해 성능을 향상시킬 수 있음
    • 항상 동일한 결과를 반환하므로 캐싱을 해도 안전

React 렌더링의 동작 방식

  • 렌더링하는 도중에 일부 데이터가 변경되면 React는 오래된 렌더링을 완료하지 않고 렌더링을 다시 시작
    • 이 과정은 컴포넌트의 순수성 덕분에 언제든지 계산을 중단해도 안전

📌 요약

  • 렌더링 중에 컴포넌트의 외부 변수가 변경되면 안 됨
    • 기존 객체를 변이하는 대신 setState 사용
    • 컴포넌트 '내부'에서 렌더링 '중에' 생성 및 변경 (= 지역 변이)
  • 대표적 사이드 이펙트인 이벤트 핸들러는 렌더링 '후에' 발생하므로 순수할 필요 없음
  • 사이드 이펙트를 위해 useEffect를 사용할 수도 있으나 최후의 수단
profile
데이터 분석가 준비 중입니다 (티스토리에 기록: https://cherylog.tistory.com/)

0개의 댓글