리액트 생태계에서 사용률이 높은 상태관리 라이브러리.
컴포넌트의 상태 관련 로직을 모듈화해서 관리할 수 있고 전역 상태 관리도 손 쉽게 할 수 있다.
리덕스로 상태 관리를 할 때 useReducer와 비슷한 리듀서 함수를 사용한다. 미들웨어를 사용해서 액션 객체가 리듀서에서 처리되기 이전에 작업을 처리해 줄 수 있다.
connect 함수를 사용하면 리더스의 상태, 액션 생성함수를 컴포넌트의 props로 받아올 수 있다.
Context API 를 사용해서 글로벌 상태를 관리 할 때에는 일반적으로 기능별로 Context를 만들어서 사용하는 것이 일반적.
상태에 변화가 생겼을 때 발생하는 것.
type 필드를 필수적으로 가지고 있어야 하고 그외는 마음대로.
{
type: "ADD_TODO",
data: {
id: 0,
text: "리덕스 배우기"
}
}
파라미터를 받아와서 액션 객체 형태로 리턴하는 함수.
export function addTodo(data) {
return {
type: "ADD_TODO",
data
};
}
// 화살표 함수로도 만들 수 있습니다.
export const changeInput = text => ({
type: "CHANGE_INPUT",
text
});
변화를 일으키는 함수. 두 가지 파라미터를 받는다.
state : 현재 상태.
action : 전달 받은 액션 객체.
function reducer(state, action) {
// 상태 업데이트 로직
return alteredState;
}
function counter(state, action) {
switch (action.type) {
case 'INCREASE':
return state + 1;
case 'DECREASE':
return state - 1;
default:
return state;
}
}
여러 개의 리듀서를 만들고 이를 합칠 수도 있다.
한 애플리케이션 당 하나의 스토어를 만들게 된다.
스토어 안에는 현재의 상태, 리듀서, 몇 가지의 내장 함수가 들어가 있다.
스토어의 내장함수 중 하나로, 액션을 발생시킨다.
디스패치로 액션을 인자로 넣어 호출하면 스토어에서는 리듀서 함수를 실행시킨다.
스토어의 내장함수로, 함수 형태의 값을 파라미터로 받는다.
액션이 디스패치 될 때마다 파라미터에 넣은 함수가 호출된다.
하나의 애플리케이션에선 단 한개의 스토어를 만들어서 사용한다.
기존의 상태는 건들이지 않고 새로운 상태를 생성하여 업데이트 해주는 방식으로 해주면, 나중에 개발자 도구를 통해서 뒤로 돌릴 수도 있고 다시 앞으로 돌릴 수도 있다.
import { createStore } from 'redux';
// createStore는 스토어를 만들어주는 함수입니다.
// 리액트 프로젝트에서는 단 하나의 스토어를 만듭니다.
/* 리덕스에서 관리 할 상태 정의 */
const initialState = {
counter: 0,
text: '',
list: []
};
/* 액션 타입 정의 */
// 액션 타입은 주로 대문자로 작성합니다.
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
const CHANGE_TEXT = 'CHANGE_TEXT';
const ADD_TO_LIST = 'ADD_TO_LIST';
/* 액션 생성함수 정의 */
// 액션 생성함수는 주로 camelCase 로 작성합니다.
function increase() {
return {
type: INCREASE // 액션 객체에는 type 값이 필수입니다.
};
}
// 화살표 함수로 작성하는 것이 더욱 코드가 간단하기에,
// 이렇게 쓰는 것을 추천합니다.
const decrease = () => ({
type: DECREASE
});
const changeText = text => ({
type: CHANGE_TEXT,
text // 액션안에는 type 외에 추가적인 필드를 마음대로 넣을 수 있습니다.
});
const addToList = item => ({
type: ADD_TO_LIST,
item
});
/* 리듀서 만들기 */
// 위 액션 생성함수들을 통해 만들어진 객체들을 참조하여
// 새로운 상태를 만드는 함수를 만들어봅시다.
// 주의: 리듀서에서는 불변성을 꼭 지켜줘야 합니다!
function reducer(state = initialState, action) {
// state 의 초깃값을 initialState 로 지정했습니다.
switch (action.type) {
case INCREASE:
return {
...state,
counter: state.counter + 1
};
case DECREASE:
return {
...state,
counter: state.counter - 1
};
case CHANGE_TEXT:
return {
...state,
text: action.text
};
case ADD_TO_LIST:
return {
...state,
list: state.list.concat(action.item)
};
default:
return state;
}
}
/* 스토어 만들기 */
const store = createStore(reducer);
console.log(store.getState()); // 현재 store 안에 들어있는 상태를 조회합니다.
// 스토어안에 들어있는 상태가 바뀔 때 마다 호출되는 listener 함수
const listener = () => {
const state = store.getState();
console.log(state);
};
const unsubscribe = store.subscribe(listener);
// 구독을 해제하고 싶을 때는 unsubscribe() 를 호출하면 됩니다.
// 액션들을 디스패치 해봅시다.
store.dispatch(increase());
store.dispatch(decrease());
store.dispatch(changeText('안녕하세요'));
store.dispatch(addToList({ id: 1, text: '와우' }));
리액트에서 useState를 사용할 때는 dispatch라는 함수로 액션을 실행했지만, 리덕스에서는 store의 내장함수에 들어있기 때문에 store를 통해 구현하게 된다.
액션 타입, 액션 생성함수, 리듀서가 모두 들어있는 자바스크립트 파일을 말한다.
리듀서, 액션 관련 코드들을 하나의 파일에 몰아서 작성하는 것을 Ducks 패턴이라고 한다.
보통의 패턴은 여러 리듀서를 만들고 index.js라는 하나의 파일에 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
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux';
import rootReducer from './modules';
const store = createStore(rootReducer); // 스토어를 만듭니다.
console.log(store.getState()); // 스토어의 상태를 확인해봅시다.
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
실질적으로 스토어로 만든 리덕스를 index 컴포넌트에서 사용하게 되는 부분.
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
const store = createStore(rootReducer); // 스토어를 만듭니다.
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
Provider로 감싼 후 store에 우리가 만든 store를 넣어주면 어떤 컴포넌트든 전부 사용할 수 있게 된다.