[말로 풀어쓴 React] 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기1

DongGu·2021년 2월 13일
0

목차


17.1 작업환경 설정

17.2 UI 준비하기

  • 17.2.1 카운터 컴포넌트 만들기
  • 17.2.2 할 일 목록 컴포넌트 만들기 준비하

17.3 리덕스 관련 코드 작성하기

  • 17.3.1 counter 모듈 작성하기
    • 17.3.1.1 액션 타입 정의하기
    • 17.3.1.2 액션 생성 함수 만들기
    • 17.3.1.3 초기 상태 및 리듀서 함수 만들기
  • 17.3.2 todos 모듈 만들기
    • 17.3.2.1 액션 타입 정의하기
    • 17.3.2.2 액션 생성 함수 만들기
    • 17.3.2.3 초기 상태 및 리듀서 함수 만들기
  • 17.3.3 루트 리듀서 만들기

17.4 리액트 애플리케이션에 리덕스 적용하기

  • 17.4.1 스토어 만들기
  • 17.4.2 Provider 컴포넌트를 사용해 프로젝트에 리덕스 적용하기
  • 17.4.3 Redux DevTools 설치 및 적용

17.5 컨테이너 컴포넌트 만들기

  • 17.5.1 CounterContainer 만들기
    • 17.5.1.1 임시함수를 통해 connect로 컴포넌트와 리덕스 연결하기
    • 17.5.1.2 액션 생성 함수를 불러와 액션 객체를 만들고 디스패치해온 것의 connect 연결
    • 17.5.1.3 mapStateToProps, mapDispatchToProps의 익명함수화
    • 17.5.1.4 리덕스의 bindActionCreators 사용
  • 17.5.2 TodosContainer 만들기
    • 17.5.2.1 Todos 컴포넌트를 위한 컨테이너인 TodosContainer 작성
    • 17.5.2.2 App 컴포넌트 내 TodosContainer 적용
    • 17.5.2.3 Todos컴포넌트에서 받아온 props 사용하기

17.6 리덕스 더 편하게 사용하기

  • 17.6.1 redux-actions
    • 17.6.1.1 counter 모듈에 적용하기
    • 17.6.1.2 todos 모듈에 적용하기
  • 17.6.2 immer

17.7 Hooks를 사용해 컨테이너 컴포넌트 만들기

  • 17.7.1 useSelector로 상태 조회하기
  • 17.7.2 useDispatch를 사용하여 액션 디스패치하기
  • 17.7.3 useStore를 사용하여 리덕스 스토어 사용하기
  • 17.7.4 TodosContainer를 Hooks로 전환하기
  • 17.7.5 useActions 유틸 Hook을 만들어서 사용하기
  • 17.7.6 connect 함수와 주요 차이점

17.1 작업환경 설정

17.2 UI 준비하기

리액트 프로젝트에서 리덕스를 사용할 때 가장 많이 사용하는 패턴은 프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 분리하는 것이다.
프레젠테이셔널 컴포넌트란 주로 상태 관리가 이루어지지 않고, props를 받아와서 화면에 UI를 보여주기만 하는 컴포넌트를 말한다. 컨테이너 컴포넌트는 리덕스와 연동되어 있는 컴포넌트로, 리덕스로부터 상태를 받아오기도 하고 리덕스 스토어에 액션을 디스패치하기도 한다.

리덕스를 사용할 때 꼭 이 패턴을 적용해야 하는 것은 아니다. 다만 패턴을 사용하면 코드의 재사용성도 높아지고, 관심사가 분리되어 UI를 작성할 때 더 집중할 수 있다.

  • 17.2.1 카운터 컴포넌트 만들기
// components/Counter.js
import React from 'react';

const Counter = ({number, onIncrease, onDecrease}) => {
  return (
    <div>
    	<hi>{number}</h1>
    	<div>
    		<button onClick={onIncrease}>+1</button>
			<button onClick={onDecrease}>-1</button>
		</div>
	</div>
  );
};
export default Counter;
// App.js
import React from 'react';
import Counter from './components/Counter';
const App = () => {
  return (
    <div>
    	<Counter number={0} />
	</div>
  );
};
export default App;
  • 17.2.2 할 일 목록 컴포넌트 만들기 준비하
    해야 할 일을 추가하고, 체크하고, 삭제할 수 있는 '할 일 목록(Todo) 컴포넌트를 만들 것이다.
// components/Todo.js
import React from 'react';
const TodoItem = ({todo, onToggle, onRemove}) => {
  return(
    <div>
    	<input type="checkbox" />
    	<span>예제 텍스트</span>
    	<button>삭제</button>
    </div>
   );
};
   
const Todos = ({
  input, // 인풋에 입력되는 텍스트
  todos, // 할 일 목록이 들어있는 객체
  onChangeInput,
  onInsert,
  onToggle,
  onRemove,
}) => {
  const onSubmit = e => {
    e.preventDefault();
  };
  return (
    <div>
    	<form onSubmit={onSubmit}>
    		<input />
    		<button type="submit">등록</button>
		</form>
		<div>
              <TodoItem/>
              <TodoItem/>
        </div>
	</div>
  );
};
export default Todos;

Todos 컴포넌트와 TodoItem 컴포넌트를 한 파일에 정의했는데, 취향에 따라 분리해도 상관없다.

//App.js
import React from 'react';
import Counter from './components/Counter';
import Todos from './components/Todos';

const App = () => {
  return (
    <div>
    	<Counter number={0} />
		<hr />
    	<Todos />
    </div>
  );
};
export default App;

17.3 리덕스 관련 코드 작성하기

이 프로젝틍서는 리덕스를 사용할 것이다. 리덕스를 사용할 떄는 액션 타입, 액션 생성 함수, 리듀서 함수 코드를 작성해야 한다.

17.3.1 counter 모듈 작성하기
  • 17.3.1.1 액션 타입 정의하기
// modules/counter.js
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

가장 먼저 해야할 작업은 액션 타입을 정의하는 것이다. 액션 타입은 대문자로 정의하고, 문자열 내용은 '모듈 이름/액션 이름'과 같은 형태로 작성한다. 프로젝트가 커졌을 때 액션의 이름이 충돌되지 않게 해준다.

  • 17.3.1.2 액션 생성 함수 만들기
    액션 타입을 정의한 다음에는 액션 생성 함수를 만들어 줘야 한다.
// modules/counter.js
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
export const increase = () => ({type: INCREASE});
export const decrease = () => ({type: DECREASE});
  • 17.3.1.3 초기 상태 및 리듀서 함수 만들기
    counter 모듈의 초기상태와 리듀서 함수를 만들 것이다.
// modules/counter.js
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

export const increase = () => ({type: INCREASE});
export const decrease = () => ({type: DECREASE});

const initialState= {
  number: 0
};

function counter(state=initialState, action){
  switch(action.type){
    case INCREASE:
      return {
        number: state.number + 1
      };
    case DECREASE:
      return {
        number: state.number -1
      };
    default:
      return state;
  }
}
export default counter;

이 모듈으 초기 상태는 number 값을 설정해줬고, 리듀서 함수에는 현재 상태를 참조하여 새로운 객체를 생성해서 반환하는 코드를 작성했다.

17.3.2 todos 모듈 만들기

  • 17.3.2.1 액션 타입 정의하기
    todos 모듈도 마찬가지로 액션 타입 정의부터 해야 한다.
// modules/todos.js
const CHANGE_INPUT = 'todos/CHANGE_INPUT'; // 인풋값을 변경함
const INSERT = 'todos/INSERT'; // 새로운 todo를 등록함
const TOGGLE = 'todos/TOGGLE'; 
const REMOVE = 'todos/REMOVE';
  • 17.3.2.2 액션 생성 함수 만들기
    액션 타입이 정의되었으면 액션 생성 함수를 만든다. counter 모듈과 달리 todos 모듈에서는 어떤 내용의 todo가 추가될지 정보가 담긴 파라미터가 필요하다. 전달받은 파라미터는 액션 객체 안에 추가 필드로 들어가게 된다.
// modules/todos.js
const CHANGE_INPUT = 'todos/CHANGE_INPUT';
const INSERT = 'todos/INSERT';
const TOGGLE = 'todos/TOGGLE';
const REMOVE = 'todos/REMOVE';

export const changeInput = input => ({
  type: CHANGE_INPUT,
  input
});

let id = 3; //insert가 호출될 때마다 1씩 더해진다.
export const insert = text => ({
  type: INSERT,
  todo: {
    id: id++,
    text,
    done: false
  }
});

export const toggle = id => ({
  type:TOGGLE,
  id
});

export const remove = id => ({
  type: REMOVE,
  id
});
  • 17.3.2.3 초기 상태 및 리듀서 함수 만들기
    액션 타입, 액션 생성함수가 정의되었으면 초기 상태와 리듀서 함수를 정의할 때다. 객체에 한 개 이상의 값이 들어가기 때문에 불변성을 유지해야 한다.spread 연산자(...)를 활용하면 된다.
// modules/todos.js
(...)
const initialState = {
 input: '',
 todos: [
   {
 		id: 1,
 		text: '리덕스 기초 배우기',
 		done: true
 	},
  	{
    	id: 2,
      	text: '리액트와 리덕스 사용하기',
        done: false
  	}
  }
};
  
function todos(state=initialState, action){
  switch(action.type){
    case CHANGE_INSERT:
      return {
        ...state,
        input: action.input
      };
    case INSERT:
      return {
        ...state,
        todos;
        state.todos.concat(action.todo)
  	  };	
  	case TOGGLE:
  		return {
          ...state,
          todos: state.todos.map(todo=> todo.id === action.id? {...todo, done: !todo.done} : todo)
        };
  	case REMOVE:
  		return {
          ...state,
          todos: state.todos.filter(todo=>todo.id !== action.id)
        };
  	default:
  		return state;
  }
}
export default todos;

17.3.3 루트 리듀서 만들기

counter, todos 리듀서 함수를 만들었다. 나중에 createStore 함수를 사용하여 ㅅ토어를 만들 때는 리듀서를 하나만 사용해야 한다. 기존에 만들었던 리듀서 함수를 하나로 합쳐야 한다. 이는 리덕스에서 제공하는 combineReducers라는 유틸함수를 사용하면 쉽게 처리가능 하다.

// modules/index.js
import {combineReducers} from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos,
});
export default rootReducer;

파일 이름을 이렇게 index.js로 설정해주면 나중에 불러올 때 디렉토리 이름까지만 입력해도 된다. index라는 파일 이름 없이 아래처럼 말이다.
import rootReducer from './modules';

17.4 리액트 애플리케이션에 리덕스 적용하기

리액트 어플리케이션에 리덕스를 적용할 차례다. 이 작업은 index.js에서 이뤄진다.

17.4.1 스토어 만들기

가장 먼저 스토어를 생성한다.

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import './index.css';
import App from './App';
import rootReducer from './modules';

const store=createStore(rootReducer);
ReactDOM.render(<App/>, document.getElementById('root'));

17.4.2 Provider 컴포넌트를 사용해 프로젝트에 리덕스 적용하기

리액트 컴포넌트에서 스토어를 사용할 수 있도록 App 컴포넌트를 react-redux에서 제공하는 Provider 컴포넌트로 감싸준다. 이 컴포넌트를 사용할 때는 store를 props로 전달해줘야 한다.

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import './index.css';
import App from './App';
import rootReducer from './modules';

const store = createStore(rootReducer);
ReactDOM.render(
  <Provider store={store}>
  	<App/>
  </Provider>,
  document.getElementById('root'),
);

17.4.3 Redux DevTools 설치 및 적용

Redux DevTools라는 리덕스 개발자 도구(크롬 확장 프로그램)를 설치한다.이후 `$ yarn add redux-devtools-extension'을 해준다.

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import {composeWithDevTools} from 'redux-devtools-extension';
import './index.css';
import App from './App';
import rootReducer from './modules';

const store = createStore(rootReducer, composeWithDevTools());
ReactDOM.render(
  <Provider store={store}>
  	<App/>
  </Provider>,
  document.getElementById('root'),
);

이것을 적용하면 리덕스 스토어 내부 상태를 볼 수 있다.

profile
코딩하는 신방과생

0개의 댓글