✏️ [React] - React의 상태 관리와 최적화 기법

정은·2024년 1월 9일
0

React의 상태 관리와 최적화 기법

1. React의 State와 UI 업데이트

  • 컴포넌트의 state 변경 시 자동으로 UI 업데이트
  • React의 핵심 원리: 상태 변화 감지하여 UI 재렌더링

2. 상태와 속성의 구성과 중요성

  • Container Component 활용하여 상태와 속성 관리
  • 코드 가독성 및 유지보수성 향상
  • 상태와 속성 명확히 구분, 데이터 흐름 효율적 관리

3. 불변성(Immutability)의 필요성

불변성은 React 앱의 성능 최적화와 상태 관리를 위해 필수적인 원칙입니다. 객체의 불변성은 객체를 변경하지 않고 새로운 객체를 생성하는 개념으로, 다음과 같은 이유로 중요성을 가집니다.

3.1 객체 불변성 개념

불변성을 유지하기 위해 객체를 변경하지 않고 새로운 객체를 생성하는 방법은 다양합니다. 이를 위해 주로 Spread 연산자를 활용한 얕은 복사가 사용됩니다.

// 얕은 복사를 통한 객체 불변성 유지
let originalObject = { name: 'John', age: 25 };
let updatedObject = { ...originalObject, age: 26 };
// originalObject는 변경되지 않고, 새로운 객체인 updatedObject가 생성됨

3.2 불변성 라이브러리 (Immer)

복잡한 객체 구조를 다룰 때, 불변성을 유지하면서 코드를 보다 간결하게 작성하기 위해 Immer 라이브러리를 활용할 수 있습니다. Immer는 기존 객체를 직접 수정하는 것처럼 보이면서 내부적으로 불변성을 유지합니다.

Immer를 사용한 예제

import { produce } from "immer";

let originalObject = { name: 'John', age: 25 };

const updatedObject = produce(originalObject, (draft) => {
  draft.age = 26;
});

// originalObject는 변경되지 않고, 새로운 객체인 updatedObject가 생성됨

Immer는 내부적으로 변경 작업을 수행하면서, 변경된 객체의 새로운 복사본을 생성하여 불변성을 지킵니다.

3.3 불변성의 필요성

불변성을 유지하는 것은 React 앱에서 상태 변화를 효과적으로 추적하고 최적화하기 위해 중요합니다.

3.3.1 렌더링 최적화

React는 상태나 속성이 변경된 경우에만 렌더링을 수행하는데, 불변성을 유지하지 않으면 객체의 어떤 부분이 변경되었는지 판단하기 어려워집니다. 따라서, 불변성을 유지함으로써 React의 렌더링 최적화 메커니즘을 적극 활용할 수 있습니다.

3.3.2 상태 변경 추적

Redux와 같은 상태 관리 라이브러리에서는 불변성을 요구합니다. 상태가 변경되면 새로운 상태 객체를 생성하여 변경 이력을 추적하고, 컴포넌트 간 데이터 흐름을 더 투명하게 만듭니다.

불변성을 유지함으로써 React 앱의 성능과 유지보수성을 향상시킬 수 있습니다. 이러한 이유로, 객체의 변경이 필요한 경우에는 언제나 새로운 객체를 생성하고, Immer와 같은 라이브러리를 활용하여 효율적으로 불변성을 유지하는 것이 바람직합니다.

4. 렌더링 최적화와 React.memo(), useCallback() 훅

렌더링 최적화는 React 애플리케이션의 성능 향상과 불필요한 렌더링을 방지하기 위해 적용되는 다양한 기법들을 의미합니다. 이를 위해 React에서는 React.memo(), useCallback(), 그리고 컴포넌트 분할과 같은 방법들을 사용합니다.

4.1. React.memo()와 Shallow Compare

  • React.memo():

    • 설명: React.memo()는 함수형 컴포넌트의 리렌더링을 방지하는 데 사용됩니다. 기존에 렌더링된 결과를 캐시하고, 속성(props)이 변경되지 않으면 이전 결과를 사용하여 불필요한 렌더링을 방지합니다.

    • 활용 예시:

      const MyComponent = React.memo((props) => {
        // 컴포넌트 로직
      });
  • Shallow Compare:

    • 설명: React.memo()는 기본적으로 얕은 비교(Shallow Compare)를 수행합니다. 즉, 속성이 객체일 경우 객체의 레퍼런스만 비교하고 내부 값은 비교하지 않습니다.

    • 활용 예시:

      const MyComponent = React.memo((props) => {
        // 컴포넌트 로직
      }, (prevProps, nextProps) => {
        // 속성(props)의 얕은 비교를 수행
        return prevProps.someValue === nextProps.someValue;
      });

4.2. useCallback() 훅

  • useCallback():

    • 설명: useCallback() 훅은 메모이제이션된 콜백 함수를 생성합니다. 이는 함수를 메모이제이션하여 속성이나 상태가 변경될 때마다 함수가 새로 생성되는 것을 방지하고, 성능을 향상시킵니다.

    • 의존배열객체로 지정해야함: useCallback 훅을 사용할 때는 해당 함수가 의존하는 상태나 속성을 명시적으로 지정하는 것이 중요합니다. 이를 의존배열객체라고 합니다. 의존배열객체를 통해 해당 함수가 어떤 상태나 속성에 의존하고 있으며, 이것이 바뀌었을 때에만 함수를 새로 생성하도록 설정합니다.

    • 활용 예시:

      const MyComponent = () => {
        const handleClick = useCallback(() => {
          // 클릭 핸들러 로직
        }, [/* 의존성 배열 */]);
      
        return (
          <button onClick={handleClick}>Click me</button>
        );
      };

4.3. 컴포넌트 분할

  • 컴포넌트 분할:

    • 설명: 큰 컴포넌트를 작은 단위로 나누어 구성하면, 부분적인 변경이 발생할 때 해당 부분만 리렌더링되도록 할 수 있습니다. 이를 통해 불필요한 전체 컴포넌트의 리렌더링을 방지합니다.

    • 활용 예시:

      const ParentComponent = () => {
        // 상태 및 로직
      
        return (
          <div>
            <ChildComponent1 />
            <ChildComponent2 />
          </div>
        );
      };

렌더링 최적화는 불필요한 리렌더링을 방지하고 성능을 향상시키는 핵심적인 원칙입니다. 위에서 언급한 기법들을 적절히 조합하여 React 애플리케이션의 성능을 최적화할 수 있습니다.

5. React-Router의 중첩 라우트

React-Router에서 제공하는 중첩 라우트는 앱의 라우팅을 더욱 세밀하게 제어할 수 있는 강력한 기능입니다.

  • 중첩 라우트의 개념:

    • 설명: 중첩 라우트는 하나의 라우터 안에서 다른 라우터를 중첩하는 것을 의미합니다. 이를 통해 앱의 특정 부분에 대한 라우팅 규칙을 독립적으로 정의할 수 있습니다.
  • 중첩 라우트의 장점:

    • 모듈성 강화: 각 섹션별로 라우팅 규칙을 독립적으로 정의하여 모듈성을 강화할 수 있습니다.
    • 유지보수성 향상: 특정 섹션의 라우팅 규칙을 분리함으로써 유지보수가 용이해집니다.
  • 중첩 인덱스 라우트:

    • 설명: 중첩 라우트를 활용하여 특정 페이지의 인덱스 라우트를 정의할 수 있습니다. 예를 들어, /products 페이지에 대한 중첩된 라우트로 /products/:id와 같은 동적 경로를 추가할 수 있습니다.
  • 구현 예시:

    // App 컴포넌트에서 중첩 라우트 정의
    <Route path="/products" element={<Products />}>
      <Route path="/" element={<ProductsList />} />
      <Route path="/:id" element={<ProductDetails />} />
    </Route>
    // Products 컴포넌트에서 Outlet 추가
    const Products = () => {
      return (
        <div>
          <h2>Products Page</h2>
          <Outlet />
        </div>
      );
    };

이렇게 중첩 라우트를 활용하면 페이지 간의 라우팅을 논리적으로 구성하고 유연하게 관리할 수 있습니다. 상위 라우트에 의해 렌더링되는 컴포넌트에 <Outlet />을 추가하여 중첩된 하위 라우트들이 올바른 위치에 렌더링되도록 할 수 있습니다.

6. React-Router가 제공하는 훅

React-Router는 다양한 훅을 제공하여 라우팅과 관련된 다양한 작업을 보다 효율적으로 수행할 수 있도록 도와줍니다.

6.1. useMatch(경로패턴):

  • 개념: 현재 요청 경로가 지정한 경로 패턴과 부합하는지 확인하는 훅입니다.
  • 결과: 부합하는 경우 PathMatch 객체를 반환합니다.

6.2. useParams():

  • 개념: URI 경로 파라미터 값을 포함한 Params 객체를 반환합니다.
  • 결과: 현재 요청의 URI 파라미터를 담은 객체를 반환합니다.

6.3. useSearchParams():

  • 개념: 현재 요청의 쿼리 문자열을 읽거나 수정할 수 있는 훅입니다.

  • 결과: 현재 요청의 쿼리 문자열을 다룰 수 있는 객체를 반환합니다.

6.4. useLocation():

  • 개념: 현재 요청된 경로 정보를 포함하는 Location 객체를 반환합니다.

  • 결과: 현재 요청의 경로 정보를 담은 객체를 반환합니다.

6.5. useNavigate():

  • 개념: 화면 전환(이동)을 위한 navigate() 함수를 반환합니다.

  • 결과: 화면 전환에 사용할 수 있는 navigate() 함수를 반환합니다.

6.6. useOutletContext():

  • 개념: 상위 경로에 상태를 저장하고 자식 경로에 대한 Outlet에 렌더링하는 자식 컴포넌트에서 상태를 이용할 수 있도록 합니다.

  • 결과: 상위 경로에서 저장한 상태를 자식 컴포넌트에서 활용할 수 있는 outletContext를 반환합니다.

0개의 댓글