리액트에서 컴포넌트를 순수하게 유지하기는 무슨 말?

렐루·2024년 5월 15일
1

리액트

목록 보기
13/20

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

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

순수성이란, => 버그를 피하도록

  1. 함수가 호출되기 전에 존재했던 객체나 변수는 변경하지 않는다.
  2. 같은 입력, 같은 출력! 입력값이 동일하다면 순수함수는 언제나 같은 값을 반환해야 한다!!

React는 이러한 컨셉 기반에 설계되었습니다. React는 작성되는 모든 컴포넌트가 순수 함수일 거라 가정합니다. 이러한 가정은 작성되는 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 />
    </>
  );

React는 개발 중에 각 컴포넌트의 함수를 두 번 호출하는 “엄격 모드”를 제공합니다. 컴포넌트 함수를 두 번 호출함으로써, 엄격 모드는 이러한 규칙을 위반하는 컴포넌트를 찾는데 도움을 줍니다.

원래 예시에서 “Guest #1”, “Guest #2”, 그리고 “Guest #3” 대신 “Guest #2”, “Guest #4”, 그리고 “Guest #6”이 어떻게 표시되었는지 확인해보세요. 원래 함수는 순수하지 않았기에 두 번 호출하는 것이 이 부분을 망가트렸습니다. 그러나 고정된 순수 버전은 함수가 매번 두 번 호출되더라도 동작합니다. 순수 함수는 연산만 하므로 두 번 호출해도 아무 것도 변경되지 않습니다.—double(2)을 두번 호출하는게 반환된 것을 변경하지 않고 y = 2x을 두 번 푸는게 y의 답을 바꾸지 않는 것 처럼, 항상 같은 입력이면 같은 출력입니다.

엄격 모드는 프로덕션에 영향을 주지 않기 때문에 사용자의 앱 속도가 느려지지 않습니다. 엄격 모드를 사용하기 위해서, 최상단 컴포넌트를 <React.StrictMode>로 감쌀 수 있습니다. 몇몇 프레임워크는 기본적으로 이 문법을 사용합니다.

코드 예시

예시 1

export default function Clock({ time }) {
  let hours = time.getHours();
  if (hours >= 0 && hours <= 6) {
    document.getElementById('time').className = 'night';
  } else {
    document.getElementById('time').className = 'day';
  }
  return (
    <h1 id="time">
      {time.toLocaleTimeString()}
    </h1>
  );
}

위의 코드에서는 직접적으로 돔을 조작하려고 합니다.
아래 리턴 부분의 마크업은 html이 아닙니다.
리액트는 가상 돔을 만들어서 이전 돔과 변경 후 돔의 모양을 가지고 둘을 비교해서 최종적으로 한번만 html 엘리먼트로 만들게 됩니다.

따라서 아래의 jsx 엘리먼트는 돔 api로 접근할 수 없습니다!

Clock.js: Cannot set properties of null (setting 'className') (6:4)


예시 2

다음 예시를 보겠습니다.

import Profile from './Profile.js';

export default function App() {
  return (
    <>
      <Profile person={{
        imageId: 'lrWQx8l',
        name: 'Subrahmanyan Chandrasekhar',
      }} />
      <Profile person={{
        imageId: 'MK3eW3A',
        name: 'Creola Katherine Johnson',
      }} />
    </>
  )
}



import Panel from './Panel.js';
import { getImageUrl } from './utils.js';

let currentPerson;

export default function Profile({ person }) {
  currentPerson = person;
  return (
    <Panel>
      <Header />
      <Avatar />
    </Panel>
  )
}

function Header() {
  return <h1>{currentPerson.name}</h1>;
}

function Avatar() {
  return (
    <img
      className="avatar"
      src={getImageUrl(currentPerson)}
      alt={currentPerson.name}
      width={50}
      height={50}
    />
  );
}

위의 코드에서는 Profile 컴포넌트가 프로퍼티를 받아도 해당 값을 Profile 컴포넌트 외부에서 관리함으로 순수함수가 깨지고 있습니다.

값을 받아서 랜더링할때 외부값을 참조하고 변경함으로 의도치 않게 값이 같아지는 버그가 발생하고 있습니다.

이를 해결하기 위해서는 내부에서 받은 props 값을 사용해야 합니다.



예시 3

export default function StoryTray({ stories }) {
  stories.push({
    id: 'create',
    label: 'Create Story'
  });

  return (
    <ul>
      {stories.map(story => (
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}

시계가 업데이트될 때마다 “Create story”가 두 번 추가됩니다. 이는 렌더링 중에 변형이 있음을 암시합니다 —엄격 모드는 컴포넌트를 두 번 호출하여 이러한 문제를 더 눈에 띄게 만들도록 해줍니다.

StoryTray 함수는 순수하지 않습니다. 전달된 stories 배열(프로퍼티!)에서 push를 호출하면 StroyTray가 렌더링을 시작하기 전에 객체를 변경합니다. 이로 인해 버그가 발생하고 예측하기가 매우 어렵습니다.

가장 간단한 해결 방법은 배열을 전혀 건드리지 않고 “Create Story”를 별도로 렌더링하는 것입니다.

profile
프론트 공부중입니다!

0개의 댓글