React_Study [ Redux ]

이준석·2023년 5월 24일
0

React_Study

목록 보기
5/6
post-thumbnail

정말 악명높은 Redux...
배우기 너무 복잡했다 ㅠ
해당 포스팅은 Redux의 기본만 다룬다

라이브러리와 Context를 사용하지 않을 때 전역적인 상태관리

  • 주로 최상위 컴포넌트인 App의 state에 선언하고 Props로 내려준다.
    • 이렇게 코드가 진행되면 코드도 복잡해지고,
    • 상태가 필요없는 컴포넌트를 중간다리 역할로 사용하게 될 수도 있다.

Context를 사용할 때 전역적인 상태관리

  • 단순히 글로벌 상태를 사용하고자 한다면 리액트의 Context API 만으로도 충분히 구현을 할 수 있다.
    • 위 코드보다 가독성이 좋아지고,
    • 중간다리 역할(Props Driling)을 할 필요가 없어졌다.
  • 단점
    • 컴포넌트에서 만약 Context의 특정 값을 의존하는 경우, 해당 값 말고 다른 값이 변경 될 때에도 컴포넌트에서는 리렌더링이 발생하게 된다.
    • 컴포넌트 트리의 각 부분에서 개별적으로 상태를 관리해야 하므로 코드가 더 복잡해질 수 있다.

Redux는 무엇이고 왜 사용할까

리덕스는 애플리케이션의 상태를 예측 가능하게 관리하기 위한 상태 관리 라이브러리
복잡한 컴포넌트 간의 데이터 공유를 쉽게 할 수 있으며, 상태 변화를 추적하기가 편리해짐

  • 더욱 향상된 성능과 미들웨어 기능,
  • 아주 편리한 개발자 도구도 지원,
  • 유지 보수성도 높여 주고 작업 효율도 극대화.

Redux 단점

  • 처음에는 사용하기 어려울 수 있으며, 추가적인 러닝 커브가 있다
  • 작은 규모의 애플리케이션에서는 상태 관리를 위한 리덕스를 도입하는 것이 비효율적일 수 있다

Flux패턴

리덕스가 사용하는 상태 관리 아키텍처
Flux 패턴은 단방향 데이터 흐름 아키텍처로, 애플리케이션의 상태 관리를 예측 가능하게 만드는 데 중점을 둡니다.

리덕스 키워드 설명

  • Action

    상태의 변경을 나타내는 개념
    {type: 'TOGGLE_VALUE'} 액션의 이름이라고 생각하면 된다.
    type외의 값들은 상태업데이트 시 참고할 값
    ex) { type: 'CHANGE_INPUT', text: '안녕하세요' }

  • Action creator

    Action을 생성하는 함수
    위 액션 객체를 사용 시 마다 직접 작성하기 번거롭고, 실수도 일어날 수 있으므로 함수로 만들어 관리
const changeInput = text => ({ 
  type: 'CHANGE_INPUT',
	text
});

  • Reducer

    액션을 만들어서 발생시키면 액션의 type값과 다른 데이터에 따라 상태를 업데이트 한다.
    일반적으로 switc문으로 type값에 따라 상태를 업데이트한다.
    첫번째 파라미터인 state엔 업데이트 되기 전 현재의 상태가 들어간다.
const initialState = {
  counter: 1
};
function reducer(state, action) {
  switch (action.type) {
    case INCREMENT:
      return {
          counter: state.counter + 1
      };
    default:
    return state;
  }
}

  • Store

    현재의 애플리케이션의 상태와 리듀서가 들어가있다.
    그리고 몇가지의 내장함수
    • Dispatch
      • 액션을 발생시키는 것
      • dispatch(action)과 같은 형태
    • Subscribe (구독)
      • subscribe 함수 안에 리스너 함수를 파라미터로 넣어서 호출하면,
      • 리스너함수는 상태가 업데이트 될 때마다 호출

Redux의 3가지 규칙

  1. 단일 스토어
    • 하나의 스토어에서 관리하는 것이 복잡성을 줄여줌
  2. 읽기 전용 상태
    • 기존에 리액트 상태과 마찬가지로 객체를 업데이트할 땐 불변성을 지켜주기 위해 새로운 객체를 만들어 교체해준다.
    • 내부적으로 데이터가 변경되는 것을 얕은 비교를 하며 성능 최적화
  3. 리듀서는순수한 함수
    • 리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받는다.
    • 파라미터 외의 값에는 의존하면 안된다.
    • 이전 상태는 절대로 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어서 반환한다.
    • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과 값을 반환해야 한다.

프레젠테이셔널, 컨테이너 컴포넌트

프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 구분하여 사용하면, 코드의 재사용성과 가독성을 높일 수 있으며, 개발과 유지보수가 더욱 편리해집니다.

  • 프레젠테이셔널 컴포넌트
    • 주로 상태 관리가 이루어지지 않고, 그저 props를 받아 와서 화면에 UI를 보여 주기만
      하는 컴포넌트
    • UI에 관련된 프레젠테이셔널 컴포넌트는 src/components 경로에 저장
  • 컨테이너 컴포넌트
    • 리덕스와 연동되어 있는 컴포넌트로, 리덕스로부터 상태를 받아 오기도 하고 리덕스 스토어에 액션을 디스패치하기도 합니다
    • 리덕스와 연동된 컨테이너 컴포넌트는 src/containers 경로에 저장

리덕스를 사용할 때 폴더 구조

  • 일반적인 구조
    • 액션 타입, 액션 생성 함수, 리듀서 코드 별로 폴더를 생성

  • Ducks 구조
    • 액션 타입, 액션 생성 함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 다 작성하는 방식
    • Ducks 패턴을 사용하여 액션 타입, 액션 생성 함수, 리듀서를 작성한 코드를 ‘모듈’이라고 한다.

나는 일반적인 구조 보다 Ducks패턴을 선호한다.
일반적인 구조는 새로운 액션을 만들 때마다 세 종류의 파일을 모두 수정해야 하기 때문에 불편하게 느껴진다.

사용 예시

ducks패턴과 프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 분리하는 하는 패턴을 같이 쓸 때 폴더 구조

components/ UI담당
  - PresentationComponent.js
containers/ UI + 상태 관리 합치기
  - ContainerComponent.js
modules/ 상태 관리 
  - myFeatureDuck.js

PresentationComponent.js:

import React from 'react';

const PresentationComponent = ({ myData, fetchData }) => {
  return (
    <div>
      <h1>My Data: {myData}</h1>
      <button onClick={fetchData}>Fetch Data</button>
    </div>
  );
};

export default PresentationComponent;

ContainerComponent.js:

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchDataAction } from '../modules/myFeatureDuck';
import PresentationComponent from '../components/PresentationComponent';

const ContainerComponent = () => {
  const dispatch = useDispatch();
  const myData = useSelector(state => state.myFeature.myData);

  useEffect(() => {
    dispatch(fetchDataAction());
  }, [dispatch]);

  const fetchData = () => {
    dispatch(fetchDataAction());
  };

  return <PresentationComponent myData={myData} fetchData={fetchData} />;
};

export default ContainerComponent;

myFeatureDuck.js:

// 액션 타입 정의
const FETCH_DATA = 'myApp/myFeature/FETCH_DATA';

// 액션 생성자 함수
const initialState = {
  myData: null,
};

// 초기 상태 정의
export function fetchDataAction() {
  return {
    type: FETCH_DATA,
  };
}

// 리듀서 함수
export default function myFeatureReducer(state = initialState, action) {
  switch (action.type) {
    case FETCH_DATA:
      // 상태 업데이트 로직
      return {
        ...state,
        myData: // 업데이트된 데이터
      };
    default:
      return state;
  }
}

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { createStore } from 'redux';
import myFeatureReducer from './modules/myFeatureDuck';
import { Provider } from 'react-redux';

const store = createStore(myFeatureReducer);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);
reportWebVitals();

다음 포스팅은 Redux에서도 로직을 작성하기 위해 저희가 공식적으로 추천하는 방법인 redux-toolkit 예정

0개의 댓글