- 공통점 : 상태를 전역적으로 관리할 수 있는 도구
Redux 의 차이점
1) 미들웨어
: 미들웨어를 사용할 수 있어서 리듀서(reducer)에 의해 처리되기 전에 특정 동작 수행 가능2) 유용한 함수와, Hooks
: Context API를 쓸 때에는 Context와 Provider를 설정하고 커스텀 Hook을 따로 만들어서 사용하여 번거로움
하지만, Redux는useSelector / useDispatch / useStore
과 같은 Hooks로 쉽게 상태를 관리3) 하나의 커다란 상태
: 기능별로 Context를 관리하는 Context API / Redux는 하나의 커다란 객체에서 상태를 관리
[ Redux에서 사용되는 키워드 ]
- 액션(Action)
- 액션 생성함수(Action Creator)
- 리듀서(Reducer)
- 스토어(Store)
- 디스패치(dispatch)
- 구독(subscribe)
(자세한 세부 설명 : https://react.vlpt.us/redux/01-keywords.html )
[ 사용하기 ]
1) reducer 만들기
2) action 및 action 생성함수 만들기( /modules/counter.js )
/* 액션 타입 만들기 */ // Ducks 패턴을 따를땐 액션의 이름에 접두사를 넣어주세요. // 이렇게 하면 다른 모듈과 액션 이름이 중복되는 것을 방지 할 수 있습니다. const SET_DIFF = 'counter/SET_DIFF'; const INCREASE = 'counter/INCREASE'; const DECREASE = 'counter/DECREASE'; /* 액션 생성함수 만들기 */ // 액션 생성함수를 만들고 export 키워드를 사용해서 내보내주세요. export const setDiff = diff => ({ type: SET_DIFF, diff }); export const increase = () => ({ type: INCREASE }); export const decrease = () => ({ type: DECREASE }); /* 초기 상태 선언 */ const initialState = { number: 0, diff: 1 }; /* 리듀서 선언 */ // 리듀서는 export default 로 내보내주세요. export default function counter(state = initialState, action) { switch (action.type) { case SET_DIFF: return { ...state, diff: action.diff }; case INCREASE: return { ...state, number: state.number + state.diff }; case DECREASE: return { ...state, number: state.number - state.diff }; default: return state; } }
( /modules/todos.js )
/* 액션 타입 선언 */ const ADD_TODO = 'todos/ADD_TODO'; const TOGGLE_TODO = 'todos/TOGGLE_TODO'; /* 액션 생성함수 선언 */ let nextId = 1; // todo 데이터에서 사용 할 고유 id export const addTodo = text => ({ type: ADD_TODO, todo: { id: nextId++, // 새 항목을 추가하고 nextId 값에 1을 더해줍니다. text } }); export const toggleTodo = id => ({ type: TOGGLE_TODO, id }); /* 초기 상태 선언 */ // 리듀서의 초기 상태는 꼭 객체타입일 필요 없습니다. // 배열이여도 되고, 원시 타입 (숫자, 문자열, 불리언 이여도 상관 없습니다. const initialState = [ /* 우리는 다음과 같이 구성된 객체를 이 배열 안에 넣을 것입니다. { id: 1, text: '예시', done: false } */ ]; export default function todos(state = initialState, action) { switch (action.type) { case ADD_TODO: return state.concat(action.todo); case TOGGLE_TODO: return state.map( todo => todo.id === action.id // id 가 일치하면 ? { ...todo, done: !todo.done } // done 값을 반전시키고 : todo // 아니라면 그대로 둠 ); default: return state; } }
3) root reducer 만들기 (/modules/index.js)
: 여러개의 리듀서를 하나로 관리하기 위한 rootReducer를 만들기import { combineReducers } from 'redux'; import counter from './counter'; import todos from './todos'; const rootReducer = combineReducers({ counter, todos }); export default rootReducer;
: 'redux' 에서 'combineReducers'를 빼서 여러 리듀서를 하나로 만든다.
4) 리액트 프로젝트에 리덕스 적용하기 (/index.js)
: 'react-redux' 사용 ($ yarn add react-redux
)import { createStore } from 'redux'; import rootReducer from './modules'; import { Provider } from 'react-redux'; const store = createStore(rootReducer); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
참고) React 정석 패턴으로 컴포넌트 만들기
: 하나의 기능을 하는 컴포넌트를 만들 때 2개로 분리하여 컨테이너에서 프리젠테이셔널 컴포넌트를 호출해서 구성
- UI에 집중하는 컴포넌트 => 프리젠테이셔널 컴포넌트
- 로직에 집중하는 컴포넌트 => 컨테이너 컴포넌트
( Counter.js ) - 프리젠테이셔널 컴포넌트
import React from 'react'; function Counter({ number, diff, onIncrease, onDecrease, onSetDiff }) { const onChange = e => { // e.target.value 의 타입은 문자열이기 때문에 숫자로 변환해주어야 합니다. onSetDiff(parseInt(e.target.value, 10)); }; return ( <div> <h1>{number}</h1> <div> <input type="number" value={diff} min="1" onChange={onChange} /> <button onClick={onIncrease}>+</button> <button onClick={onDecrease}>-</button> </div> </div> ); } export default Counter;
( CounterContainer.js ) - 컨테이너 컴포넌트
import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Counter from '../components/Counter'; import { increase, decrease, setDiff } from '../modules/counter'; function CounterContainer() { // useSelector는 리덕스 스토어의 상태를 조회하는 Hook입니다. // state의 값은 store.getState() 함수를 호출했을 때 나타나는 결과물과 동일합니다. const { number, diff } = useSelector(state => ({ number: state.counter.number, diff: state.counter.diff })); // useDispatch 는 리덕스 스토어의 dispatch 를 함수에서 사용 할 수 있게 해주는 Hook 입니다. const dispatch = useDispatch(); // 각 액션들을 디스패치하는 함수들을 만드세요 const onIncrease = () => dispatch(increase()); const onDecrease = () => dispatch(decrease()); const onSetDiff = diff => dispatch(setDiff(diff)); return ( <Counter // 상태와 number={number} diff={diff} // 액션을 디스패치 하는 함수들을 props로 넣어줍니다. onIncrease={onIncrease} onDecrease={onDecrease} onSetDiff={onSetDiff} /> ); } export default CounterContainer;
5) 컴포넌트에서 사용하기 ( useSelector() / useDispatch() )
: 로직에 집중하는 컨테이너 컴포넌트에서 사용한다!import { useSelector, useDispatch } from 'react-redux'; ... const { number, diff } = useSelector(state => ({ number: state.counter.number, diff: state.counter.diff })); ... const dispatch = useDispatch(); // 각 액션들을 디스패치하는 함수들을 만드세요 const onIncrease = () => dispatch(increase()); const onDecrease = () => dispatch(decrease()); const onSetDiff = diff => dispatch(setDiff(diff)); ...
[ 리덕스 개발자도구 적용 ]
1) (크롬 웹 스토어) 확장 프로그램 설치
검색 : Redux Devtools
https://chrome.google.com/webstore/category/extensions?hl=ko&
2) 프로젝트에 'redux-devtools-extension' 설치
$ yarn add redux-devtools-extension
3) index.js 수정
... import { composeWithDevTools } from 'redux-devtools-extension'; ... const store = createStore(rootReducer, composeWithDevTools()); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );