React

리덕스 이해하기 - 1

JoyYellow 2022. 6. 14. 01:17

개념 미리 정리하기

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를 사용할 수도 있다.