[React Docs Beta] 1-4. memo

2023. 3. 10.·🎨 프론트엔드 공부/React & Next

React Docs (beta) 번역 및 정리

더보기

Built-in React APIs

1. createContext
2. forwardRef
3. lazy

4. memo

 

📍memo

컴포넌트의 props가 변경되지 않는 경우, 리렌더링을 스킵하게 할 수 있다

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

 

📍memo(Component, arePropsEqual?)

- memo api로 memoization할 컴포넌트를 래핑한다

- memoized된 버전의 컴포넌트는 리렌더링되지 않는다 (부모 컴포넌트가 리렌더링되어도)

단, props가 바뀌면 리렌더링됨

- memoization 완벽하게 보장되지는 않으며 성능 최적화 용도로만 사용해야 한다

import { memo } from 'react';

const SomeComponent = memo(function SomeComponent(props) {
  // ...
});

 

✅매개변수

컴포넌트

- memoization할 컴포넌트

- forwardRef 처럼 모든 유효한 컴포넌트가 매개변수로 가능

 

(옵셔널) arePropsEqual

- 2개의 인수를 갖는 함수

1. old props

2. new props

 

- old props와 new props가 같으면 true 반환

 

✅반환값

- memoized component 반환

 

📍예제) props가 바뀌지 않으면 리렌더링 생략

- 일반적으로 리액트에서는 부모 컴포넌트가 리렌더링되면, 그 자식 컴포넌트들도 리렌더링된다

- memo api를 사용하여 props가 바뀌지 않으면 부모 컴포넌트가 리렌더링되어도 리렌더링되지 않는 컴포넌트를 만들 수 있다

- 이 때, 컴포넌트가 memoized 된다고 한다

 

아래의 Greeting 컴포넌트는 name props가 바뀌지 않으면 부모 컴포넌트가 리렌더링되어도 리렌더링되지 않는다

const Greeting = memo(function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
});

export default Greeting;

 

⭐리액트의 컴포넌트들은 항상 순수 렌더링 로직(순수 함수 처럼)을 가져야 한다

- 즉, props, state, context가 바뀌지 않으면 항상 같은 아웃풋를 가져야한다

 

이는 memoized 컴포넌트도 마찬가지이다, props, 자체 state, 사용중인 context가 변경되면 리렌더링된다

 

- App.js

import { memo, useState } from 'react';

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

const Greeting = memo(function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  return <h3>Hello{name && ', '}{name}!</h3>;
});

 

name을 props로 갖는 Greeting memoized 컴포넌트는 name이 바뀌면 리렌더링된다

 

 

📍모든 곳에 memo를 추가해야 할까?

- memo를 과용하면 가독성을 해칠 수 있다

- 또한 props 값이 조금이라도 바뀌면 리렌더링되므로, 생각보다 memoization 혜택을 볼 수 있는 경우가 제한적이다

 

⭐memoization을 아낄 수 있는 몇 가지 원칙

 

1. When a component visually wraps other components, let it accept JSX as children. This way, when the wrapper component updates its own state, React knows that its children don’t need to re-render.

 

2. 가급적 전역 대신 지역 상태를 사용하고, 일시적인(변화가 일회성) 상태가 컴포넌트 트리 또는 전역 상태 라이브러리에 포함되어 있지 않게 할 것.

 

3. 순수 리렌더링 로직을 지킬 것

- props, state, context가 동일한데 리렌더링에 따라 다른 결과를 가져온다면, 이는 버그이다

 

4. 상태를 업데이트하는 불필요한 effect를 제거하라

- React 앱에서의 대부분의 성능 문제는 컴포넌트가 계속 렌더링되게 하는 effect (상태를 변화) 때문에 발생한

 

5. effect 에서 불필요한 의존성(의존성 배열) 을 제거하라

- 메모이제이션 대신 객체나 함수를 컴포넌트 외부로 빼는 것이 더 효과적인 경우가 많다

 

리액트 데브 툴즈의 Profiler 기능을 사용하면 어떤 컴포넌트를 메모이제이션했을 때 가장 효과적일지 확인할 수 있다

 

📍예제) 자체 state가 변경되면 리렌더링되는 memoized 컴포넌트

- props 이외에도 자체 state가 변경되어도 리렌더링된다

 

import { memo, useState } from 'react';

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

const Greeting = memo(function Greeting({ name }) {
  console.log('Greeting was rendered at', new Date().toLocaleTimeString());
  
  // memoized component가 갖는 자체 state
  // greeting 값이 변경되면 memoization을 무시하고 리렌더링된다
  
  const [greeting, setGreeting] = useState('Hello');
  return (
    <>
      <h3>{greeting}{name && ', '}{name}!</h3>
      <GreetingSelector value={greeting} onChange={setGreeting} />
    </>
  );
});

function GreetingSelector({ value, onChange }) {
  return (
    <>
      <label>
        <input
          type="radio"
          checked={value === 'Hello'}
          onChange={e => onChange('Hello')}
        />
        Regular greeting
      </label>
      <label>
        <input
          type="radio"
          checked={value === 'Hello and welcome'}
          onChange={e => onChange('Hello and welcome')}
        />
        Enthusiastic greeting
      </label>
    </>
  );
}

 

📍예제) context를 사용하는 memoized compoenet 업데이트하기

- memoization 되더라도, 사용중인 context가 변경되면(subscription 하고 있는 value가 변경되면 리렌더링된다

 

import { createContext, memo, useContext, useState } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  const [theme, setTheme] = useState('dark');

  function handleClick() {
    setTheme(theme === 'dark' ? 'light' : 'dark'); 
  }

  return (
    <ThemeContext.Provider value={theme}>
      <button onClick={handleClick}>
        Switch theme
      </button>
      <Greeting name="Taylor" />
    </ThemeContext.Provider>
  );
}

const Greeting = memo(function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  
  // subscription 중인 아래 theme 값이 변경되면 리렌더링 발생
  
  const theme = useContext(ThemeContext);
  return (
    <h3 className={theme}>Hello, {name}!</h3>
  );
});

 

📍props 변경을 최소화하기

- React에서는 기본적으로 두 컴포넌트의 props가 다른지 비교할 때 shallow compare(Object.is 메서드로 비교)를 사용한다

- memo를 최대한 활용하려면, props가 바뀌는 시간을 최소화해야 한다

- 예를 들어, props가  객체인 경우, useMemo를 사용하여 부모 컴포넌트가 매번 해당 객체를 다시 만들지 않도록 한다

 

function Page() {
  const [name, setName] = useState('Taylor');
  const [age, setAge] = useState(42);

  // useMemo를 사용하여 person 객체를 생성
  const person = useMemo(
    () => ({ name, age }),
    [name, age]
  );

  return <Profile person={person} />;
}

// Profile 컴포넌트는 person 객체가 바뀌지 않으면 (즉, name와 age state가 바뀌지 않으면)
// 새로 만들어지지 않는다
const Profile = memo(function Profile({ person }) {
  // ...
});

 

- props 변경을 최소화하는 더 좋은 방법은 컴포넌트가 해당 props에서 필요한 최소한의 정보만 받게하는 것이다

- 아래처럼 줄이면 된다

function Page() {
  const [name, setName] = useState('Taylor');
  const [age, setAge] = useState(42);
  return <Profile name={name} age={age} />;
}

const Profile = memo(function Profile({ name, age }) {
  // ...
});

 

'🎨 프론트엔드 공부/React & Next' 카테고리의 다른 글
  • [React Docs] 2-0. Built-in React Hooks
  • [React Docs Beta] 1-5. startTransition
  • [React Docs Beta] 1-2. forwardRef (2)
  • [React Docs Beta] 1-2. forwardRef (1)
지식물원
지식물원
지식이 자라는 식물원!
  • 지식물원
    지식물원
    지식물원
  • 전체
    오늘
    어제
    • 분류 전체보기 (516)
      • 🎨 프론트엔드 공부 (253)
        • JS & TS (92)
        • HTML & CSS (22)
        • React & Next (49)
        • Vue & Nuxt (22)
        • 기타 (68)
      • 🤓 기술 학습 & 공부 기록 (116)
        • Node.js (0)
        • Python (37)
        • 백엔드 (0)
        • 딥러닝 (1)
        • 컴퓨터 일반 (72)
        • 개발 인프라 (6)
      • 👨‍💻 프로젝트 경험 (6)
        • Work (0)
        • Toy (6)
      • ⚙️ 개발 팁 & 노하우 (21)
        • 프론트엔드 (6)
        • 기타 (15)
      • ☕️ 커리어 & 인터뷰 준비 (88)
        • 코딩 테스트 (88)
      • 📰 기술 트렌드 & 생각 정리 (4)
      • 📚 기타 (25)
        • 마케팅 (15)
        • 비개발서적 (10)
  • 블로그 메뉴

    • 태그
  • 링크

  • 공지사항

    • 모바일 접속 시 코드 하이라이팅 깨질 때
  • 인기 글

  • hELLO· Designed By정상우.v4.10.3
지식물원
[React Docs Beta] 1-4. memo
상단으로

티스토리툴바