Redux 기본알기

#
React
#
Redux
#
Study

정말 악명높은 Redux... 배우기 너무 복잡했다 ㅠ 해당 포스팅은 Redux의 기본 단어와 개념만 다룹니다.

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

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

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

  • Context API를 사용하면 Context를 만들어 단 한 번에 원하는 값을 받아 와서 사용할 수 있습니다.
    • 위 코드보다 가독성이 좋아지고,
    • 중간다리 역할을 할 필요가 없어졌습니다.
  • 단점
    • 컴포넌트에서 만약 Context의 특정 값을 의존하는 경우, 해당 값 말고 다른 값이 변경 될 때에도 컴포넌트에서는 리렌더링이 발생하게 됩니다.
    • 컴포넌트 트리의 각 부분에서 개별적으로 상태를 관리해야 하므로 코드가 더 복잡해질 수 있습니다.

Redux는 무엇이고 왜 사용할까

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

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

Redux 단점

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

Redux를 사용하기 적절한 때

  • 계속해서 바뀌는 상당한 양의 데이터가 있다.
  • 상태를 위한 단 하나의 근원이 필요하다.
  • 최상위 컴포넌트가 모든 상태를 가지고 있는 것은 더 이상 적절하지 않다.

Flux패턴

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

리덕스 키워드 설명

Action

상태의 변경을 나타내는 개념 {type: 'TOGGLE_VALUE'} 액션의 이름이라고 생각하면 됩니다.

type외의 값들은 상태업데이트 시 참고할 값 ex) { type: 'CHANGE_INPUT', text: '안녕하세요' }

Action creator

Action을 생성하는 함수 위 액션 객체를 사용 시 마다 직접 작성하기 번거롭고, 실수도 일어날 수 있으므로 함수로 만들어 관리합니다.

1const changeInput = (text) => ({ 2 type: "CHANGE_INPUT", 3 text, 4}); 5

Reducer

Reducer는 변화를 일으키는 함수입니다. 액션을 만들어서 발생시키면 액션의 type값과 다른 데이터에 따라 상태를 업데이트 합니다. 일반적으로 switch문으로 type값에 따라 상태를 업데이트합니다. 첫번째 파라미터인 state엔 업데이트 되기 전 현재의 상태가 들어갑니다.

1const initialState = { 2 counter: 1, 3}; 4function reducer(state, action) { 5 switch (action.type) { 6 case INCREMENT: 7 return { 8 counter: state.counter + 1, 9 }; 10 default: 11 return state; 12 } 13} 14

Store

현재의 애플리케이션의 상태와 리듀서를 관리하는 곳 입니다. 그리고 몇가지의 내장함수를 가지고 있습니다.

Dispatch

  • 액션을 발생시키는 함수라고 생각하면 되며,
  • dispatch(action) 같은 형태 입니다.

Subscribe (구독)

  • subscribe 함수 안에 리스너 함수를 파라미터로 넣어서 호출하면,
  • 리스너함수는 상태가 업데이트 될 때마다 호출됩니다.

Redux의 3가지 규칙

  1. 단일 스토어

    • 하나의 스토어에서 관리하는 것이 복잡성을 줄여줌
  2. 읽기 전용 상태

    • 기존에 리액트 상태과 마찬가지로 객체를 업데이트할 땐 불변성을 지켜주기 위해 새로운 객체를 만들어 교체해준다.
    • 내부적으로 데이터가 변경되는 것을 얕은 비교를 하며 성능 최적화
  3. 리듀서는순수한 함수

    • 리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받는다.
    • 파라미터 외의 값에는 의존하면 안된다.
    • 이전 상태는 절대로 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어서 반환한다.
    • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과 값을 반환해야 한다.

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

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

  • 프레젠테이셔널 컴포넌트

    • 주로 상태 관리가 이루어지지 않고, 그저 props를 받아 와서 화면에 UI를 보여 주기만 하는 컴포넌트 입니다.
    • UI에 관련된 프레젠테이셔널 컴포넌트는 src/components 경로에 저장을 합니다.
  • 컨테이너 컴포넌트

    • 리덕스와 연동되어 있는 컴포넌트로, 리덕스로부터 상태를 받아 오기도 하고 리덕스 스토어에 액션을 디스패치(액션을 발생)하기도 합니다.
    • 리덕스와 연동된 컨테이너 컴포넌트는 src/containers 경로에 저장을 합니다.

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

일반적인 구조

  • 액션 타입, 액션 생성 함수, 리듀서 코드 별로 폴더를 생성합니다.

Ducks 구조

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

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

사용 예시

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

1components/ UI담당 2 - PresentationComponent.js 3containers/ UI + 상태 관리 합치기 4 - ContainerComponent.js 5modules/ 상태 관리 6 - myFeatureDuck.js 7

PresentationComponent.js

1import React from "react"; 2 3const PresentationComponent = ({ myData, fetchData }) => { 4 return ( 5 <div> 6 <h1>My Data: {myData}</h1> 7 <button onClick={fetchData}>Fetch Data</button> 8 </div> 9 ); 10}; 11 12export default PresentationComponent; 13

ContainerComponent.js

1import React, { useEffect } from "react"; 2import { useDispatch, useSelector } from "react-redux"; 3import { fetchDataAction } from "../modules/myFeatureDuck"; 4import PresentationComponent from "../components/PresentationComponent"; 5 6const ContainerComponent = () => { 7 const dispatch = useDispatch(); 8 const myData = useSelector((state) => state.myFeature.myData); 9 10 useEffect(() => { 11 dispatch(fetchDataAction()); 12 }, [dispatch]); 13 14 const fetchData = () => { 15 dispatch(fetchDataAction()); 16 }; 17 18 return <PresentationComponent myData={myData} fetchData={fetchData} />; 19}; 20 21export default ContainerComponent; 22

myFeatureDuck.js

1// 액션 타입 정의 2const FETCH_DATA = 'myApp/myFeature/FETCH_DATA'; 3 4// 액션 생성자 함수 5const initialState = { 6 myData: null, 7}; 8 9// 초기 상태 정의 10export function fetchDataAction() { 11 return { 12 type: FETCH_DATA, 13 }; 14} 15 16// 리듀서 함수 17export default function myFeatureReducer(state = initialState, action) { 18 switch (action.type) { 19 case FETCH_DATA: 20 // 상태 업데이트 로직 21 return { 22 ...state, 23 myData: // 업데이트된 데이터 24 }; 25 default: 26 return state; 27 } 28} 29

index.js

1import React from "react"; 2import ReactDOM from "react-dom/client"; 3import { createStore } from "redux"; 4import myFeatureReducer from "./modules/myFeatureDuck"; 5import { Provider } from "react-redux"; 6 7const store = createStore(myFeatureReducer); 8const root = ReactDOM.createRoot(document.getElementById("root")); 9root.render( 10 <React.StrictMode> 11 <Provider store={store}> 12 <App /> 13 </Provider> 14 </React.StrictMode> 15); 16reportWebVitals(); 17

참고

리액트를 다루는 기술 [개정판]