개념 미리 정리하기
1. 액션
상태에 어떠한 변화가 필요하면 액션이란 것이 발생한다. 이는 하나의 객체로 표현되는데, 액션 객체는 다음과 같은 형식으로 이루어져 있다. 액션 객체는 type필드를 반드시 가지고 있어야 한다. 이 값을 액션의 이름이라고 생각하면 된다. 그리고 그 외의 값들은 나중에 상태 업데이트를 할 때 참고해야할 값이다.
{
type: 'ADD_TODO',
data {
id: 1,
text: '리덕스 배우기'
}
}
2. 액션 생성 함수
액션 생성 함수는 액션 객체를 만들어 주는 함수이다. 어떤 변화를 일으켜야 할 때마다 액션 객체를 만들어야 하는데 매번 액션 객체를 직접 작성하기 번거로울 수 있고, 만드는 과정에서 실수로 정보를 놓칠 수 있다. 이러한 일을 방지하기 위해 이를 함수로 만들어서 관리한다.
function addTodo(data) {
return {
type: 'ADD_TODO',
data
}
}
3. 리듀서
리듀서는 변화를 일으키는 함수이다. 액션을 만들어서 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아 온다. 그리고 두 값을 참고하여 새로운 상태를 만들어서 반환해준다.
const initialState = {
count: 1
}
function reducer(state = initialState, action) {
switch(action.type) {
case INSERT:
return {
counter: state.counter + 1
}
default:
return state;
}
}
4. 스토어
프로젝트에 리덕스를 적용하기 위해 스토어를 만든다. 한개의 프로젝트는 단 하나의 스토어만 가질 수 있다. 스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에도 몇 가지 중요한 내장 함수를 지닌다.
5. 디스패치
디스패치는 스토어 내장 함수 중 하나이다. 디스패치는 '액션을 발생시키는 것'이라고 이해하면 된다. 이 함수는 dispatch(action)과 같은 형태로 액션 객체를 파라미터로 넣어서 호출한다. 이 함수가 호출되면 스토어는 리듀서 함수를 실행시켜서 새로운 상태를 만들어 준다.
6. 구독
구독도 스토어의 내장 함수 중 하나이다. subscribe 함수 안에 리스너 함수를 파리미터로 넣어서 호출해 주면, 이 리스너 함수가 액션이 디스패치되어 상태가 업데이트 될 때마다 호출된다.
const listener = () => {
console.lo('상태가 업데이트됨');
}
cosnt unsubscribe = store.subscribe(listener);
unsubscribe(); // 추후 구독을 비활성화 할 때 함수를 호출
1. 액션 타입, 액션 생성 함수, 초기 상태, 리듀서 함수 정의
modulse/Counter.js
// 액션 타입 정의
const INSERT = 'counter/INSERT';
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;
}
}
2. 리액트 컴포넌트에 리덕스를 연동한다.
containers/CounterContainer.js
// 리듀서 호출하기
import { connect } from 'react-redux'; // 리액트 컴포넌트에 리덕스를 연동한다.
import Counter from '../components/Counter';
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
)
};
// 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수
const mapStateToProps = state => ({
number: state.counter.number
})
// 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수
const mapDispatchToProps = dispatch => ({
increase: () => {
console.log('increase');
},
decrease: () => {
console.log('decrease');
}
})
// connect 함수를 호출하고 나면 또 다른 함수가 반환된다.
// 반환된 함수에 컴포넌트를 파라미터로 넣어 주면 리덕스와 연동된 컴포넌트가 만들어진다.
// const makeContainer = connect(mapStateToProps, mapDispatchToProps);
// makeContainer(타깃 컴포넌트)
export default connect(
mapStateToProps,
mapDispatchToProps,
)(CounterContainer);
// containers/CounterContainer.js
// 이번에는 액션함수를 불러와서 디스패치해 준다.
import { increase, decrease } from '../modules/counter';
const mapDispatchToProps = dispatch => ({
increase: () => {
dispatch(increase())
},
decrease: () => {
dispatch(decrease())
}
})
// 액션을 디스패치하기 위해 각 액션 생성 함수를 호출하고 dispatch로 감싸는 작업이 번거롭다.
// redux에서 제공하는 bindActionCreators 유틸 함수를 사용하면 간편하다.
import { bindActionCreators } from 'redux'
export defaul connect(
state => ({
number: state.counter.number,
}),
dispatch =>
bindActionCreators(
{
increase,
decrease,
}
)
)(CounterContainer)
// bindActionCreators를 사용하는 것 보다 더욱 간편한 방법이 있다.
// 함수 형태가 아닌 액션 생성 함수로 이루어진 객체 형태로 넣어주는 것이다.
// 아래와 같이 두 번재 파라미터를 아예 객체 형태로 넣어주면 connect 함수가 내부적으로
// bindeActionCreators 작업을 대신 해 준다.
export defaul connect(
state => ({
number: state.counter.number,
}),
{
increase,
decrease,
}
)(CounterContainer)
3. redux-actions 라이브러리 도입
액션 생성 함수, 리듀서를 작성할 때 redux-actions라느는 라이브러리와 immer 라이브러리를 활용하면 리덕스를 편하게 사용할 수 있다.
// 라이브러리 설치
yarn add redux-actions
modules/counter.js
import { createAction, handleActions } from 'redux-actions';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
const initialState = {
number: 0
}
const counter = handleActions(
{
[INCREASE]: (state, action) => ({ number: state.number + 1 }),
[DECREASE]: (state, action) => ({ number: state.number - 1 }),
},
initialState,
)
4. immer 라이브러리 도입
import produce from 'immer';
const todos = handleActions(
{
[CHANGE_INPUT]: (state, { payload: input }) =>
produce(state, draft => {
draft.input = input;
}),
}),
[INSERT]: (state, { payload: todo }) =>
produce(state, draft => {
draft.todos.push(todo)
}),
}),
[TOGGLE]: (state, { payload: id }) =>
produce(state, draft => {
const todo = draft.todos.find(todo => todo.id === id);
todo.done = !todo.done;
}),
}),
[REMOVE]: (state, { payload: id }) =>
produce(state, draft => {
const index = draft.todos.findIndex(todo => todo.id === id);
draft.todos.splice(index, 1);
}),
}),
},
initialState,
)
4. Hooks를 사용하기
리덕스 스토어와 연동된 컨테이너 컴포넌트를 만들 때 connect 함수를 사용하는 대신 react-redux에서 제공하는 Hooks를 사용할 수도 있다.
'React' 카테고리의 다른 글
[React]리덕스 이해하기 - 2 (0) | 2022.06.25 |
---|---|
리덕스 이해하기 (0) | 2022.06.08 |
JWT를 통한 회원 인증 시스템 구현하기 - 1 (0) | 2022.06.06 |
state, props (0) | 2021.10.29 |
'react-dom' / JSX (0) | 2021.10.26 |