리액트의 상태관리

Mason Choi·2023년 12월 29일

React

목록 보기
2/2
post-thumbnail

🔄 리액트의 상태관리

상태관리의 중요성

React는 단방향으로 바인딩하는 라이브러리

  • 부모 ➡️ 자식 방향으로만 state를 props로 전달함.
  • 자식의 props를 부모에게 전달하는 방법은 존재하지 않는다.

  • 그렇다고 자식 컴포넌트에서 부모 컴포넌트의 State를 바꿀 수 없는 것은 아님.

    자식에게 부모의 state를 modify 할 수 있는 setState 함수를 props로 넘겨준다.
    state management tool(recoil, Jotai 등)을 활용한다.

Parent.js

import React from "react";
import MyComponent from "./MyComponent";

function App() {
    return(
         <MyComponent propValue="헬로 리액트!">Bye 리액트!</MyComponent>
    );
}
export default App;

Children.js

import React from "react";

function MyComponent(props) {
    return(
        <div>
            {props.propValue}, {props.children}
        </div>

    );
}
export default MyComponent;

  • 다음과 같은 예시처럼, 부모 component에서 건내준 propValue를 자식 component의 propValue로 내려보내 자식 component에서 활용할 수 있음.
  • 하지만 조금만 규모가 큰 app에 대해선 상태 관리에 어려움을 겪에 됩니다. 자식을 내려보내는 depth가 조금만 깊어지면 효율적이지 않음. ( Props drilling )
  • 그렇기 때문에 State management tool이 존재 !

Props drilling🪛

**Props Drilling** → props를 하위 컴포넌트로 전달하는 과정에서 몇 개의 컴포넌트를 뚫고 들어가는 형태를 의미.

props를 전달하는 컴포넌트가 적을 땐 문제가 없으나, 많은 컴포넌트들을 뚫고 state를 전달해야 한다면 그 props를 추적하기 어려워진다.

**Redux**, **Recoil**,**Jotai** 등의 전역 상태 관리 라이브러리를 사용하여 이와 같은 props drilling을 방지할 수 있다.

🚀 State management tool

🐝Redux

  • 가장 많이 사용하는 상태관리 라이브러리
  • 통계에 따르면 약 48%의 개발자들이 리액트 프로젝트에서 현재 Redux를 사용

그래서 얘 왜 써 ?
대규모 애플리케이션에 강력한 상태 관리 솔루션을 제공함
useState는 로컬 상태 관리에서 주로 사용하는 반면, Redux전역 상태 관리에서 주로 사용
즉, useState 컴포넌트 범위의 상태 관리를 위한 것이고Redux 애플리케이션 전체 범위의 상태 관리를 위한 것
하지만 Redux를 쓰는 것이 항상 좋지만은 않다. 초기 설정과 사용 방법이 어렵기도 함.
프로젝트의 규모나 상태 관리의 필요성에 따라 적절한 도구를 선택하는 것이 중요

  • 사용 예시
// store.ts
import { configureStore } from '@reduxjs/toolkit';

// 상태 타입 정의
interface CounterState {
  value: number;
}

// 초기값 설정
const initialState: CounterState = {
  value: 0,
};

// Reducer 함수 정의. 액션 타입에 따라 상태를 변화시킴.
function counterReducer(state = initialState, action: { type: string }): CounterState {
  switch (action.type) {
    case 'counter/incremented':
      return { ...state, value: state.value + 1 };
    case 'counter/decremented':
      return { ...state, value: state.value - 1 };
    default:
      return state;
  }
}

// 스토어 생성.
const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
  • 우선적으로 Reducer Store를 정의해준다.
// actions.ts
export const increment = () => ({
  type: 'counter/incremented',
});

export const decrement = () => ({
  type: 'counter/decremented',
});
  • 다음으론 Action 생성자를 정의해준다.
// Counter.tsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from './store';
import { increment, decrement } from './actions';

const Counter: React.FC = () => {
  // useSelector를 사용해 스토어의 상태를 선택.
  const count = useSelector((state: RootState) => state.counter.value);
  // useDispatch를 사용해 Action을 Dispatch하는 함수를 가져올 수 있음.
  const dispatch: AppDispatch = useDispatch();

  return (
    <div>
      <div>Value: {count}</div>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>
  );
};

export default Counter;
  • Component에서 Store와 연결해줌.
// App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import Counter from './Counter';

const App: React.FC = () => {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
};

export default App;
  • Provider ****컴포넌트를 사용해 전체 애플리케이션에 스토어를 제공한다.
  • **Counter** 컴포넌트에서 버튼을 클릭할 때마다, Redux 스토어의 상태가 업데이트된다.
  • 이는 **Counter** 컴포넌트에 반영
  • 실제 프로젝트에서는 더 복잡하고 구조화된 방식으로 코드를 작성하게 됨.

🐝 Recoil

  • Facebook이 개발한 상태 관리 라이브러리

**atoms****selectors**로 관리

  • Atoms: 애플리케이션의 상태의 일부를 나타내며, 어떤 컴포넌트에서든 상태를 읽거나 쓸 수 있음.
  • Selectors: 파생된 상태를 나타내며, atom의 상태에 의존하고, atom의 상태가 바뀔 때마다 자동으로 갱신.

React의 컨텍스트에 더 가깝게 설계되었으며,Atom을 구독하는 컴포넌트만 리렌더링되어 성능 이점을 제공

Recoil은 상태를 전역적으로 관리하면서도 React의 Hooks 패턴과 잘 어울려, 친숙한 패턴으로 상태 관리를 할 수 있게 해줌.

  • 예시
    // state.ts
    import { atom } from 'recoil';
    
    export const counterState = atom({
      key: 'counterState', // 고유한 ID(다른 atoms/selectors와 구분을 위함)
      default: 0, // 기본값
    });
    • Recoil의 상태를 정의

      // Counter.tsx
      import React from 'react';
      import { useRecoilState } from 'recoil';
      import { counterState } from './state';
      
      const Counter: React.FC = () => {
        const [count, setCount] = useRecoilState(counterState);
      
        return (
          <div>
            <div>Count: {count}</div>
            <button onClick={() => setCount(count + 1)}>Increment</button>
            <button onClick={() => setCount(count - 1)}>Decrement</button>
          </div>
        );
      };
      
      export default Counter;
    • 이 상태를 사용하는 컴포넌트를 생성

      // App.tsx
      import React from 'react';
      import { RecoilRoot } from 'recoil';
      import Counter from './Counter';
      
      const App: React.FC = () => {
        return (
          <RecoilRoot>
            <Counter />
          </RecoilRoot>
        );
      };
      
      export default App;
    • RecoilRoot를 사용하여 Recoil 상태 트리를 제공

🐝 Jotai

  • Recoil에 영감을 받았지만 더 간단하고 미니멀한 API를 제공

전역 상태를 **atoms을 사용해 관리하는데, 이들은 Recoil에서처럼 상태의 작은 단위로 작동
목표는 최소한의 API로 상태를 쉽게 관리하는 것**
작은 규모의 프로젝트를 진행할 시 유리

  • Typescript 기반이고 업데이트가 주기적이다 *
  • 예시
```tsx
// state.ts
import { atom } from 'jotai';

export const counterAtom = atom(0); // 초기값이 0인 atom
```

- **우선적으로 atom을 정의**
// Counter.tsx
import React from 'react';
import { useAtom } from 'jotai';
import { counterAtom } from './state';

const Counter: React.FC = () => {
  const [count, setCount] = useAtom(counterAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
      <button onClick={() => setCount(c => c - 1)}>Decrement</button>
    </div>
  );
};

export default Counter;
  • Atom 사용 컴포넌트
    • Jotai의 **useAtom** 훅을 사용하여 atom의 값을 읽고 설정
    • **setCount** 함수를 호출하여 상태를 업데이트하면, 해당 atom을 사용하는 모든 컴포넌트가 자동으로 업데이트
    • 이처럼 간결하고 직관적인 방식으로 상태를 관리할 수 있도록 해주며, 특히 작거나 중간 규모의 프로젝트에서 유용

⭐️ 결론

  • 대규모 서비스를 진행하던지 혹은 특수한 상황을 제외한다면, 어떠한 상태관리 방법을 사용하든 개발자가 원하는 동일한 전역상태관리 효과를 나타낼 것이다.
  • 꼭 무엇을 써야만 한다기 보다는, 전역상태관리 라이브러리가 각자 이러한 특징들을 가지고 있고 현재 진행하는 서비스의 모든 상황을 고려했을 때 가장 적절한 라이브러리를 선택하여 사용하는 것이 중요함.
  • 이번 프로젝트에선 Jotai를 써서 상태 관리를 진행하기로 하여 사용해보고 추가적으로 Jotai에 대한 내용 기재하겠음.
  • Zustand도 요새 많이 쓰인다던데 이것도 더 공부해서 기재해보도록 하겠습니다 !

[지원 : 동국대학교 SW교육원]

profile
본립도생

0개의 댓글