team logo icon

React에 대해

리액트의 상태관리와 렌더링을 효율적으로 할 수 있는 방법들을 살펴보자.

좋은 상태관리란 무엇일까? 에 대해서 알아보기 전에,

상태와 상태관리란 무엇일까? 부터 알아보겠습니다.

1. 상태란 무엇일까?


리액트에서 상태란 컴포넌트 내부에서 변할 수 있는 값입니다.

예를 들어, 어떤 홈페이지에서 로그인이 되어있어야 접근할 수 있는 페이지가 있다고 가정해 해봅시다.

그렇다면, 로그인이 되어 있는 상태로그인이 되어있지 않은 상태로 구분할 수 있습니다.

더 자세한 이해를 위해서, 뷰와 코드를 통해서 알아보겠습니다.

상태는 우리가 사용하는 곳에서도 여러 곳에서 사용되고 있는데요,

팜스프링에서도 간단히 예시를 찾을 수 있었습니다.


아래는 팜스프링의 메인 화면입니다.

로그인을 하지 않았을 때는 로그인, 무료로 시작하기 버튼이 보이지만,

로그인이 되어있는 상태에서는 대시보드라는 버튼이 보입니다.


이를 코드로 한 번 살펴볼까요?

import React, {useState} from 'react'

function Component() {
  const [isLogIn, setIslogIn] = useState(false);
  
  return (
    {
      isLogin ? 
        <button>대시보드</button> : 
        <>
            <button>로그인</button>
            <button>무료로 시작하기</button> 
        </>
    }
  )
}

이렇게 코드를 작성할 수 있습니다!

isLogin이라는 상태가 true면 대시보드 버튼이 화면에 나오고, false면, 로그인 , 무료로 시작하기 를 볼 수 있을 것입니다!

이처럼 컴포넌트 내부에서 변할 수 있는 값이 상태(State) 라고 할 수 있습니다. 그리고 상태는 React State, props 혹은 상태 관리 라이브러리 등을 사용해서 관리할 수 있습니다.

조금 더 이해하기 쉽게 정리하자면,

상태란, "유저와의 상호작용을 위한, 변할 수 있는 데이터" 라고 할 수 있습니다.

그렇다면 상태관리는 이런 것들을 조작하고 다루는 작업이겠죠.


그럼, 이제 생각해봅시다. 좋은 상태관리란 무엇이고, 어떻게 할 수 있을까요?

2. 좋은 상태관리란?


좋은 상태관리가 있다면, 나쁜 상태관리도 있을 텐데요.

나쁜 상태관리를 지양한다면, 좋은 상태관리에 조금 더 가까워질 것 같은데,

그렇다면 어떤 기준으로 우리가 상태관리에 대해서 좋고, 나쁨을 따질 수 있을까요?

상태관리는 앞서 말한 것 처럼 유저들과 상호작용을 통해 데이터를 변경시키고, 또 변할 수도 있는데

그 과정에서 아래는 흔히 우리가 흔히 겪을 수 있는 문제입니다.

  • 불필요한 리렌더링

  • 의도하지 않은 UI/UX

  • 유지 보수하기 힘든 코드

이런 문제들이 최대한 발생하지 않도록 상태관리를 작성한다면, 좋은 상태관리에 가까워질 수 있지 않을까요?

2-1. Prop Drilling이란 무엇일까?


Prop Drilling이란 props를 '하위 컴포넌트로 전달하는 용도로만 쓰이는' 컴포넌트들을 거치면서 React Component 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정입니다.

다시말해 props를 통해 데이터를 전달하는 과정에서 중간 컴포넌트는 그 데이터가 필요하지 않음에도 자식 컴포넌트에 전달하기 위해 props를 전달해야하는 과정을 말합니다.

아래의 예시를 살펴볼까요?

import React from 'react';

function App() {
  return <GrandFather />;
}

function GrandFather() {
  const name = '이재훈 할아버지';
  return <Mother grandFatherName={name} />;
}

function Mother(props) {
  return <Child grandFatherName={props.grandFatherName} />;
}

function Child({ grandFatherName }) {
  return <div>{grandFatherName}</div>;
}

export default App;

이 코드는 GrandFather에서 Mother로, Mother에서 Child로  props drilling을 하고있습니다.

위의 코드 예시에서는 시작컴포넌트~끝컴포넌트사이 전달컴포넌트가 1개 뿐이라 props의 전달과정이 쉽게 보이지만,  props 전달이 10개가 넘어가는 과정을 거쳐야 한다면, 코드를 읽을 때 prop을 추적하기가 힘들어지고, 유지보수도 어려워지지 않을까요?


즉, application의 규모가 커지면 커질수록, 컴포넌트의 숫자가 많아지고, props drilling에 따라 props도 많은 구간을 이동해야 하기 때문에 코드가 훨씬 복잡해집니다.


그리고, 아래와 같은 문제가 발생하게 됩니다.

  • 필요보다 많은 props를 전달하다가, 컴포넌트 분리하는 과정에서 필요하지 않은 props가 계속 남거나 전달되는 문제

  • props 전달을 누락했는데 default props가 사용되어 props의 미전달을 인지하기 어려운 문제

  • props의 이름이 전달중에 변경되어 데이터를 추적하기 어려워지는 문제


그럼 이런 문제를 우리는 어떻게 해결해야할까요?

2-2. props Drilling의 문제를 해결해보자!

이런 문제를 해결하는 여러 방법들을 알아보겠습니다.


1. 렌더링 될 컴포넌트를 불필요하게 여러 컴포넌트로 나누지 않는다

React는 단 하나의 컴포넌트에 application 전체를 작성하더라도 기술적인 제약이 없습니다. 따라서 불필요한 컴포넌트 쪼개기를 할 필요가 없습니다. 컴포넌트를 재사용해야할 상황을 기다렸다 분할해도 괜찮으며, 불필요한 props drilling을 방지할 수 있습니다.

 

2. defaultprops를 필수 컴포넌트에 사용하지 않는다.

deafultProps를 사용하면 필요한 props가 전달되지 못한 상황임에도 오류가 가려지게 됩니다. 따라서 defaultProps를 필수적이지 않은 컴포넌트에만 사용하면 props drilling으로 인한 문제를 막을 수 있습니다.


3. 가능한 관련성이 높은 곳에 state를 위치한다.

어떤 데이터가 application의 특정 위치에서만 필요하면 최상위 컴포넌트에 state를 위치시키는게 아닌, 해당 state를 필요로하는 컴포넌트들의 최소 공통 부모 컴포넌트에서 관리하는 것이 효율적입니다.


4. 상태관리 도구를 사용한다.

데이터를 필요로하는 컴포넌트가 props drilling의 깊숙히 위치한다면, React의 Context API를 사용하거나, Redux, Recoil 등의 외부 전역 상태관리 라이브러리를 사용해서 문제를 해결할 수 있습니다.


5. Children을 사용한다.

children을 사용하여 리팩토링을 진행하면, 하나의 컴포넌트에서 값을 관리하고, 그 값을 하위요소로 전달할 때 코드추적이 어려워지지 않게됩니다.


그럼 간단하게 위 코드에서 children을 사용해서, props drilling을 피하는 예시도 살펴볼까요?

import React from 'react';

function App() {
  const name = '이재훈 할아버지';
  return (
  <GrandFather>
    <Mother>
    	<Child grandFathername={name}/>
    </Mother>
  </GrandFather>);
}

function GrandFather() {
  return (
    <div>
    	<h3>1번 컴포넌트<h3>
        { children }
    </div>
  );
}

function Mother(props) {
  return (
    <div>
    	<h3>2번 컴포넌트<h3>
        { children }
    </div>
  );
}

function Child({ name }) {
  return <div>{ name }</div>;
}

export default App;

위처럼 코드를 작성하게 되면,

기존의 GrandFather에서 Mother로 Mother에서 Child로 props로 내려줘야 했던 것을,

GrandFather 컴포넌트에서 Child 컴포넌트의 props로 바로 작성할 수 있게 되며 직관적인 추적이 가능하게 됩니다.

3. 렌더링을 효과적으로 관리하려면?

렌더링을 효과적으로 관리하는 법을 알아보기 전에, 리액트의 렌더링이 어떤 건지 한 번 살펴볼까요?

렌더링(Rendering)이란 코드로 정의된 내용이 실제 브라우저 화면에 그려지는 과정을 의미합니다.

즉, 브라우저 화면에 그릴 내용을 만드는 과정이라고 볼 수 있는데, 리액트는 이때 컴포넌트의 props와 state를 기반으로 UI를 만듭니다.


그럼 우리는 왜 렌더링을 효과적으로 관리해야할까요?

렌더링은 연산이 필요한 작업이기 때문에 불필요한 렌더링이 계속 발생한다면 이는 성능 저하의 원인이 됩니다. 따라서, 불필요한 렌더링을 줄이는 것이 매우 중요하고, 이에 따라서 효과적으로 관리해야합니다.


그렇다면, 렌더링을 효과적으로 관리할 수 있는 방법

즉, 렌더링 최적화 할 수 있는 방법에는 어떤 것들이 있을까요?

렌더링을 최적화하기 위해서는 우리는 리렌더링에 대해서 알아야합니다.

그럼, 리렌더링은 뭔지 한 번 살펴볼까요?


리렌더링

리액트는 페이지가 처음 로드될 때 렌더링을 한 번 진행하고, 이후 특정 조건이 발생하면 조건이 발생한 부분에 대해서 렌더링을 다시 진행하는데 이걸 리렌더링이라 합니다.

렌더링이 화면을 그리는 것이면, 리렌더링은 화면이 바뀌면 그에 따라 화면을 다시 그리는 것을 말합니다.


리렌더링이 일어나는 조건은 다음과 같습니다.

1. 컴포넌트 내부의 상태 변경(setState, dispatch)은 해당 컴포넌트의 리렌더링을 발생시킵니다.

2. props로 전달받는 값의 변경은 해당 컴포넌트의 리렌더링을 발생시킵니다.

3. 부모 컴포넌트의 리렌더링은 자식 컴포넌트의 리렌더링도 발생시킵니다.

4. 중앙 상태값(Redux store, Mobx store, Recoil atom, Context API 등)의 변화는 리렌더링을 발생시킵니다.


그럼 여기서, 3번을 주목해 볼텐데요,

부모 컴포넌트의 변경은 바로 아래의 자식 컴포넌트만 리렌더링시킬까요?


바로 아래의 자식 컴포넌트만 리렌더링이 발생하는지 확인하기 위해 간단한 코드를 만들었습니다.

버튼을 누르면 부모 컴포넌트의 상태가 “render” 라는 값으로 바뀌고 상태가 변경되었으니 리렌더링이 일어납니다. 바로 아래의 자식 컴포넌트만 리렌더링이 발생한다면 버튼을 눌렀을 때 Child1 Rendered!라는 콘솔만 찍혀야 합니다. 결과가 어떻게 될까요?

첫 6줄은 처음 렌더링 때 찍힌 콘솔입니다. 당연히 Child2가 렌더링이 됩니다. 버튼을 누르자 Child2 Rendered! 또한 같이 찍혔습니다. 즉, 부모 컴포넌트의 렌더링은 하위 모든 컴포넌트의 렌더링에 영향이 갑니다.

그럼 여기서 알 수 있는 점은, 부모 컴포넌트의 리렌더링을 최소화하면 전체 리렌더링을 최소화할 수 있다는 점입니다.


간단하게 부모 컴포넌트의 리렌더링만 최소화해도, 전체 리렌더링을 획기적으로 줄일 수 있습니다.

이러한 방법 외에도 리액트에는 렌더링 최적화 할 수 있는 여러가지 방법들이 있습니다.

  • React.memo

  • useMemo

  • useCallback

이 hook들의 자세한 이용법은 아래의 링크를 참고하시면 더 자세히 알 수 있습니다. 한 번 살펴보시길 바랍니다.

https://velog.io/@khy226/useMemo%EC%99%80-useCallback-%ED%9B%91%EC%96%B4%EB%B3%B4%EA%B8%B0


컴포넌트의 렌더링을 생각하면서 개발한다면, 렌더링 최적화와 더불어 더 좋은 사용자 경험을 가지고 갈 수 있을 것입니다 :)


아래는 제가 작년에 기술공유 세션에서 발표한 리액트 렌더링 최적화에 대한 발표자료입니다.

제가 겪었던 실제 사례와 함께 렌더링 최적화에 대해서 소개하고 있는데, 한 번 살펴보면 좋으실 것 같습니다~!

https://docs.google.com/presentation/d/1MTDUSNIHWt3LuRbJtOs4kTso0uqYHNTx/edit?usp=drive_link&ouid=109857443479441301973&rtpof=true&sd=true


최신 아티클
lighthouse에 대해
문성희
|
2024.05.13
lighthouse에 대해
lighthouse에 대해
prettier, eslint, styleLint에 대해
이진
|
2024.05.10
prettier, eslint, styleLint에 대해
4주차 공유과제
Article Thumbnail
박채연
|
2024.05.10
Prettier, ESLint, StyleLint
prettier, eslint, stylelint