본문 바로가기

FrontEnd

리액트 성능 최적화 : children props를 이용하여 리렌더링 방지

반응형

재미있는 글을 봤습니다.

https://kentcdodds.com/blog/optimize-react-re-renders

 

One simple trick to optimize React re-renders

Without using React.memo, PureComponent, or shouldComponentUpdate

kentcdodds.com

부모 컴포넌트가 리렌더링되면 자식들은 다 리렌더링 되는거 아닌가요?

그런데 children들이 리렌더링 되지 않는 경우가 있다고요?

 

React는 리렌더링 시 전달된 children 객체에 대하여 얕은 비교를 수행합니다.

즉, 전달된 children이 동일한 객체이면 리렌더링을 수행하지 않습니다.

(React.memo는 1단계 깊이 비교를 수행하는 것으로 알고 있습니다.)

 

2022.07.09 추가 : 원문도 약간 애매한듯 해서, 제가 알아본 내용을 더해 설명합니다.

보통 컨텍스트 프로바이더 컴포넌트를 만들 때, 다음과 같이 코딩합니다.

(이 때, 두 개로 쪼개는 이유와, 최적화가 되는 이유에 대해선 다른 곳에서도 설명이 부족한 것으로 알고 있습니다.)

const AppProvider = ({ children }) => {
  const [state, setState] = React.useState(1);
  return (
    <AppStateContext.Provider value={state}>
      <AppDispatchContext.Provider value={setState}>
        {children}
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  );
};

위 코드를 리렌더링 관점에서 분석해봅시다.

부모가 리렌더링되면 자식들이 다 리렌더링 된다.

두 개의 프로바이더와 children이 리렌더링 될까요?


정답은 2개의 프로바이더는 확실하다. children은 모른다 입니다.

 

JSX는 객체일 뿐입니다.

그리고 우리는 우리는 단지 React.createElement로 객체를 전달하고 있습니다.

return 하는것은 중첩된 오브젝트 리터럴로, 항상 새로 만들어집니다.

따라서 리액트는 항상 새로운 객체를 전달받기 때문에, 두 개의 Provider에 대해 반드시 리렌더링을 수행합니다.

 

그러면 children은 어떤가요? prop으로 전달받기 때문에 전에 사용하던 객체를 재사용 할 수 있습니다.

리액트의 핵심 철학 중 하나는 "불변"입니다. 즉, 데이터가 변경된 객체를 참조하는 변수는 항상 다른 어드레스를 참조합니다.

(good) 따라서 다음과 같이 사용하게 되면, Child1만 컨텍스트를 소비할 경우 Child2는 리렌더링되지 않습니다.

// good
const AppWrapper1 = () => {
  return (
    <div>
      <AppProvider>
        <Child1 />
        <Child2 />
      </AppProvider>
    </div>
  );
};

컨텍스트를 사용하는 child1만 리렌더링

(bad) 하지만 아래와 같이 사용하게 되면 둘 다 리렌더링하게 되죠

const AppWrapper2 = () => {
  const [state, setState] = React.useState(1);
  return (
    <AppStateContext.Provider value={state}>
      <AppDispatchContext.Provider value={setState}>
        <Child1 />
        <Child2 />
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  );
};

둘 다 리렌더링 되는 모습

 

응용 : 상속보다는 합성

아래와 같은 웹 페이지를 컴포넌트 단위로 나누어 볼까요?

보통 아래와 같이 생각하겠죠?

function App() {
  return (
    <div>
      <MainNav />
      <Homepage />
    </div>
  )
}
function MainNav() {
  return (
    <div>
      <GitHubLogo />
      <SiteSearch />
      <NavLinks />
      <NotificationBell />
      <CreateDropdown />
      <ProfileDropdown />
    </div>
  )
}
function Homepage() {
  return (
    <div>
      <LeftNav />
      <CenterContent />
      <RightContent />
    </div>
  )
}
function LeftNav() {
  return (
    <div>
      <DashboardDropdown />
      <Repositories />
      <Teams />
    </div>
  )
}
function CenterContent() {
  return (
    <div>
      <RecentActivity />
      <AllActivity />
    </div>
  )
}
function RightContent() {
  return (
    <div>
      <Notices />
      <ExploreRepos />
    </div>
  )
}

하지만 위와 같은 컴포넌트 구조는 리렌더링 워터폴을 발생시킬 수 있습니다.

위에서 배운 지식 + 상속보다는 합성 원리를 적용해 볼까요?

레이아웃에 슬롯을 뚫어놓고 컴포넌트를 집어넣는다 상상해 보세요

function App() {
  return (
    <div>
      <MainNav>
        <GitHubLogo />
        <SiteSearch />
        <NavLinks />
        <NotificationBell />
        <CreateDropdown />
        <ProfileDropdown />
      </MainNav>
      <Homepage
        leftNav={
          <LeftNav>
            <DashboardDropdown />
            <Repositories />
            <Teams />
          </LeftNav>
        }
        centerContent={
          <CenterContent>
            <RecentActivity />
            <AllActivity />
          </CenterContent>
        }
        rightContent={
          <RightContent>
            <Notices />
            <ExploreRepos />
          </RightContent>
        }
      />
    </div>
  )
}
function MainNav({children}) {
  return <div>{children}</div>
}
function Homepage({leftNav, centerContent, rightContent}) {
  return (
    <div>
      {leftNav}
      {centerContent}
      {rightContent}
    </div>
  )
}
function LeftNav({children}) {
  return <div>{children}</div>
}
function CenterContent({children}) {
  return <div>{children}</div>
}
function RightContent({children}) {
  return <div>{children}</div>
}

 

컴포넌트 깊이가 낮아져서  <App /> -> <AllActivity /> 까지의 프롭 전달이

<App /> -> <Homepage /> -> <CenterContent /> -> <AllActivity /> 3단계에서 1단계로 줄었네요.

props drilling 문제도 해결했습니다!

참고

https://epicreact.dev/one-react-mistake-thats-slowing-you-down/

 

One React mistake that's slowing you down

Simplify and speed up your app development using React composition

epicreact.dev

https://kentcdodds.com/blog/optimize-react-re-renders

 

One simple trick to optimize React re-renders

Without using React.memo, PureComponent, or shouldComponentUpdate

kentcdodds.com

https://reactjs.org/docs/composition-vs-inheritance.html

 

Composition vs Inheritance – React

A JavaScript library for building user interfaces

reactjs.org

 

 

 

 

반응형