[fastcampus] Ch11. Redux로 상태관리하기(1)

productuidev·2022년 7월 27일
0

React Study

목록 보기
50/52
post-thumbnail

1. Redux Basic

1-1) Redux 개요

1) 단일 Store를 만드는 방법

  • 단일 Store : 파란 공 설계
  • import redux
  • action 정의
  • action 사용하는 reducer 만들기
  • reducer 합치기
  • 최종적으로 합쳐진 reducer를 인자로 단일 Store 만들기

2) React에서 Store를 사용하는 방법

  • import react-redux
  • connect 함수(HOC)를 이용해서 컴포넌트에 연결

3) Redux install

npm create-react-app redux-start
npm i redux : Store를 만드는 라이브러리

1-2) Action - 액션

Action

  • Redux에서 Action이란 사실 그냥 객체(Object)임
  • 두 가지 형태의 Action
{type: 'TEST'} // payload 없는 액션
{type: 'TEST', params: 'hello'} // payload 있는 액션
  • type만이 필수 Property, type은 String

Action 생성자

function 액션 생성자(...args) { return 액션; }
  • 액션을 생성하는 함수를 Action Creator라고 함
  • 함수를 통해 Action을 생성해서 Action Object를 리턴 (실수 방지를 위해 Action을 만들어내는 함수를 만들어서 사용)
  • createTest('hello'); // {type: 'TEST', params: 'hello'} 리턴

Action이 하는 일

  • 액션 생성자를 통해 액션을 만들어 냄
  • 만들어 낸 액션 객체를 리덕스 스토어에 보냄
  • 리덕스 스토어가 액션 객체를 받으면 스토어 상태 값이 변경됨
  • 변경된 상태 값에 의해 상태를 이용하고 있는 컴포넌트가 변경됨 (컴포넌트를 변경하는 요인)
  • 액션은 스토어에 보내는 일종의 인풋이라 생각할 수 있음

Action을 준비하기 위해서는

1) 액션 타입 정의 후 변수로 만들기
강제는 아님
타입을 문자열로 넣기에는 실수 유발 가능성
미리 정의한 변수 사용하면 스펠링에 주의를 덜 기울여도 됨

2) 액션 객체를 만들어 내는 함수 만들기
하나의 액션 객체를 만들기 위해 하나의 함수를 만들기
액션 타입은 미리 정의한 타입 변수로부터 가져와서 사용

src/redux/actions.js

export const ADD_TODO = 'ADD_TODO';

export function addTodo(todo) {
  return {
    type: ADD_TODO,
    todo,
  };
}

1-3) Reducers - 리듀서

Reducer

  • 액션을 주면 그 액션이 적용되어 달라진(안 달라질 수도) 결과를 만들어 줌
  • 그냥 함수이다.
    Pure Function : 같은 인풋을 받으면 같은 결과를 내는 함수
    Immutable : original state와 바뀐 state가 별도의 객체로 만들어져야 함
  • 리듀서를 통해 state가 달라졌음을 redux가 인지하는 방식
function 리듀서(previousState, action) {
	return newState;
}
  • 액션을 받아서 state를 리턴하는 구조
  • 인자로 들어오는 previousState와 리턴되는 newState는 다른 참조를 가지도록 해야함 (= 각각 immutable해야 한다는 의미)

src/redux/reducers.js

import { ADD_TODO } from './actions';

// state 모습 구상 (배열)
// ['코딩', '휴식', ''];

const ininitialState = []; // 미리 초기값

export function todoApp(previousState = ininitialState, action) {
  // 초기값을 설정해주는 부분
  // if(previousState === undefined){
  //   return [];
  // }

  if(action.type === ADD_TODO) {
    return [...previousState, action.todo];
  }

  return previousState;
}

1-4) createStore

redux 라이브러리를 이용해서 createStore import

Store를 만드는 함수

위의 예시의 파란 공을 만드는 함수

const store = createStore(리듀서);

createStore<S>(
	reducer: Reducer<S>,
    preloadedState: S,
    enhancer?: StoreEnhancer<S>
):Store<S>;

src/redux/store.js

import { createStore } from 'redux';
import { todoApp } from './reducers';

const store = createStore(todoApp);

export default store;

root: index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import store from './redux/store';

console.log(store); 

콘솔에 출력된 객체 확인

console.log(store.getState()); // []
  • store.getState() 호출하면 [] 빈 배열 출력
  • createStore할 때 todoApp이 최초 실행되면서 todoApp의 previousState = initialState (최초 상태로 결과)

Store의 상태 변화 시키기

getState

root: index.js

import { addTodo } from './redux/actions';

console.log(store);
console.log(store.getState());

// store 상태 변경 시키기
store.dispatch(addTodo("coding"));
console.log(store.getState());

Store의 변경상태 구독

subscribe

// store 상태 구독
const unsubscribe = store.subscribe(()=>{
  console.log(store.getState());
});

// console.log(store);

// store 상태 변경 시키기
store.dispatch(addTodo("coding"));
store.dispatch(addTodo("reading"));
store.dispatch(addTodo("eating"));

unsubscribe();

Store 정리

  • store.getState();
  • store.dispatch(액션); store.dispatch(액션생성자());
  • const unsubscribe = store.subscribe(()=>{});
  • 리턴이 unsubscribe라는 점
  • unsubscribe()하면 제거
  • store.replaceReducer(다른 리듀서);

1-5) combineReducers

store가 복잡해질 때 처리하는 방법

Action1 (ADD_TODO)

root: index.js

store.subscribe(()=>{
  console.log(store.getState());
});

src/redux/reducers.js

import { ADD_TODO } from './actions';

// [{text: '코딩', done:false}, {text: '휴식', done:false}]
const ininitialState = []; // 미리 초기값

export function todoApp(previousState = ininitialState, action) {
  if(action.type === ADD_TODO) {
    return [...previousState, {text:action.text, done:false}];
  }

  return previousState;
}

src/redux/actions.js

// {type: ADD_TODO, text: '할일'}
export function addTodo(text) {
  return {
    type: ADD_TODO,
    text,
  };
}

root: index.js

import store from './redux/store';
import { addTodo } from './redux/actions';

store.subscribe(()=>{
  console.log(store.getState());
});

store.dispatch(addTodo('할일'));

Action2 (COMPLETE_TODO)

done: true로 바꿔주는 또 하나의 액션 생성

src/redux/actions.js

export const COMPLETE_TODO = 'COMPLETE_TODO';

// {type: COMPLETE_TODO, index: 3} <- 배열의 id값이 나오도록
export function completeTodo(index) {
  return {
    type: COMPLETE_TODO,
    index,
  };
}

액션에 대응되는 reducer의 로직 만들기

src/redux/reducers.js

import { ADD_TODO, COMPLETE_TODO } from './actions';

...

  // COMPLETE_TODO
  if(action.type === COMPLETE_TODO) {
    return previousState.map((todo, index)=>{
      if (index === action.index) {
        return { ...todo, done:true };
      }
      return todo;
    });
  }

root: index.js

import store from './redux/store';
import { addTodo, completeTodo } from './redux/actions';

store.subscribe(()=>{
  console.log(store.getState());
});

store.dispatch(addTodo("할일"));
store.dispatch(completeTodo(0));

두번째 객체 상태 변경

3) 현재 View에서 어떤 것을 보여줄 것인지 필터링 기억시키기

src/redux/reducers.js

import { ADD_TODO, COMPLETE_TODO } from './actions';

// {todos: [{text: '코딩', done:false}, {text: '휴식', done:false}], filter: 'ALL'}
const ininitialState = {todos: [], filter: 'ALL'};

export function todoApp(previousState = ininitialState, action) {

  // ADD_TODO
  if(action.type === ADD_TODO) {
    return {
      ...previousState, // 필터를 가지고 있도록 처리
      todos: [...previousState.todos, {text:action.text, done:false}] };
  }

  // COMPLETE_TODO
  if(action.type === COMPLETE_TODO) {
    return {
      ...previousState, // 필터를 가지고 있도록 처리
      todos: previousState.todos.map((todo, index)=>{
        if (index === action.index) {
          return { ...todo, done:true };
        }
        return todo;
    })};
  }

  return previousState;
}

대응되는 actions 추가

src/redux/actions.js


export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_COMPLETE = 'SHOW_COMPLETE';

export function showALL() {
  return { type: SHOW_ALL }
}

export function showComplete() {
  return { type: SHOW_COMPLETE }
}

로직 추가
src/redux/reducers.js

import { ADD_TODO, COMPLETE_TODO, SHOW_COMPLETE } from './actions';

...

  if (action.type === SHOW_COMPLETE) {
    return {
      ...previousState,
      filter: "COMPLETE",
    }
  }

  if (action.type === SHOW_ALL) {
    return {
      ...previousState,
      filter: "ALL",
    }
  }
  

출력 확인
root: index.js

import store from './redux/store';
import { addTodo, completeTodo, showComplete } from './redux/actions';

store.subscribe(()=>{
  console.log(store.getState());
});

store.dispatch(addTodo("할일"));
store.dispatch(completeTodo(0));
store.dispatch(showComplete());

=> 단일 스토어인 경우와 복잡해지는 경우 (쪼개기)
=> 별개의 리듀서 처리

4) combineReducers

  • combineReducer를 활용해서 모으기
  • 각각의 property로 지정해서 셋팅
  • todos 로직 + filter 로직 합쳐서 combine
  • 각각의 initialState 지정해주기 (각각 별도로 초기상태)

src/redux/reducer.js

import { ADD_TODO, COMPLETE_TODO, SHOW_ALL, SHOW_COMPLETE } from './actions';
import { combineReducers } from 'redux';

const ininitialState = {todos: [], filter: 'ALL'};

// store에서 받을 수 있도록 초기 상태를 reducer 위로 옮기기
const todoIntialState = ininitialState.todos;
const filterInitialState = ininitialState.filter;

const reducer = combineReducers({
  todos: todosReducer,
  filter: filterReducer,
});

export default reducer;

// todoApp을 todoReducer로 변경
// todoReducer에는 todo에 관련된 로직만 남기기
// 배열 상태로 바꿔주기
// {todos: [{text: '코딩', done:false}, {text: '휴식', done:false}], filter: 'ALL'}

function todosReducer(previousState = todoIntialState, action) {
  if(action.type === ADD_TODO) {
    return [...previousState, {text:action.text, done:false}];
  }

  if(action.type === COMPLETE_TODO) {
    return previousState.map((todo, index)=>{
      if (index === action.index) {
        return { ...todo, done:true };
      }
      return todo;
    });
  }

  return previousState;
}

// todoApp 복사해서 filterReducer로 변경
// filterReducer에는 filter에 관련된 로직만 남기기
// 객체가 아닌 값으로 리턴하기

function filterReducer(previousState = filterInitialState, action) {
  if (action.type === SHOW_COMPLETE) {
    return "COMPLETE";
  }

  if (action.type === SHOW_ALL) {
    return "ALL";
  }

  return previousState;
}

src/redux/store.js

combine한 reducer를 store에 import

import { createStore } from 'redux';
import todoApp from './reducers';

const store = createStore(todoApp);

export default store;

5) 각각의 파일로 분리

하나의 reducer에 있을 경우 복잡하므로 각각의 파일로 나눔

src/redux/reducers/todos.js

import { ADD_TODO, COMPLETE_TODO } from '../actions'; // path 변경

const ininitialState = []; // 초기값 변경

export default function todos(previousState = intialState, action) {
  if(action.type === ADD_TODO) {
    return [...previousState, {text:action.text, done:false}];
  }

  if(action.type === COMPLETE_TODO) {
    return previousState.map((todo, index)=>{
      if (index === action.index) {
        return { ...todo, done:true };
      }
      return todo;
    });
  }

  return previousState;
}

src/redux/reducers/filter.js

import { SHOW_ALL, SHOW_COMPLETE } from '../actions'; // path 변경

const ininitialState = [];

export default function filter(previousState = ininitialState, action) {
  if (action.type === SHOW_COMPLETE) {
    return "COMPLETE";
  }

  if (action.type === SHOW_ALL) {
    return "ALL";
  }

  return previousState;
}

src/redux/reducers/reduce.js

import { combineReducers } from 'redux';
import todos from './todos';
import filter from './filter';

const reducer = combineReducers({
  todos,
  filter,
});

export default reducer;

(모두 옮겼으니) 기존에 있던 src/redux/reduces.js는 삭제 후 src/redux/store.js 변경

import { createStore } from 'redux';
import todoApp from './reducers/reducer';

const store = createStore(todoApp);

export default store;

💬 이전에 갖고 있던 패스트캠퍼스 한 번에 끝내는 프론트엔드 개발(Online) 강의-2021에서 Redux 기초 정리

💬 강의흐름처럼 처음에 한 덩어리에서 개별로 분리하거나 아니면 전체 구상 후에 개별 분리된 것을 하나로 합쳐서 만드는 식인 거 같은데 그럴려면 처음에 어떻게 설계하고 나눌지 판단을 해야하는 거 같다. (기초개념인데도 복잡..)

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글