JavaScript 상태 관리 라이브러리인 Redux를 배우다.
1. Redux 정의
1 - 1. 기존 상태관리 문제점, redux 사용하는 이유
기존의 여러 컴포넌트에서 state를 관리하기 위해서 Context API를 사용해서 관리했다. 하지만 Context API를 사용해서 state를 관리하다 보면 문제가 생긴다. 여러 컴포넌트에서 사용하는 state가 많아질수록, 컴포넌트의 구조가 복잡해질수록 Context 컴포넌트를 만들어내고 감싸줘야 한다. 극단적으로 보면 그림 1과 같은 현상이 발생한다.
Context는 변하지 않는 state를 관리할 때 사용한다. 즉 상태가 아닌 값인 변수를 설정하는 도구이다. 그리고 Context API는 많은 state를 관리하기에 적합하지 않다. props를 하위 컴포넌트로 넘겨 넘겨줘버리는 Drilling 현상을 방지하기 위해서 사용되기에 적절한 방법이다.
1 - 2. redux 원리
상태를 관리하는 라이브러리인 redux가 우리가 원하던 방법이다. Redux는 다른 파일에서 state를 관리한다. 정확히 말하자면 그냥 저장소 느낌이다. 내가 느끼기엔 스타링크처럼 원하는 곳에서 사용하고 싶은 것을 요청해서 스타링크가 요청에 응답해서 반환하는 것처럼 redux도 비슷하게 작동된다. 컴포넌트에서 바로 store로 요청하는 것이 아니고 상태 관리 과정이 있다.
2. Redux 설치 및 사용 방법
2 - 1. Redux Toolkit 설치
https://redux-toolkit.js.org/introduction/getting-started
npm i @reduxjs/toolkit react-redux
Redux를 사용하기 위해서 Redux Toolkit을 설치해야 한다. 2가지 이상의 모듈을 설치할 때는 그냥 띄어쓰기하고 설치하면 여러 개를 한 번에 설치할 수 있다.
2 - 2. redux 사용방법
src 폴더 내에 redux폴더를 생성한 후 redux폴더 내에 저장소인 store.js와 state를 변경할 함수 파일들을 만들어주면 된다. 그럼 일단 가장 먼저 해야 할 일은 store.js파일을 생성하는 것이다.
// store.js
import {configureStore, getDefaultMiddleware} from '@reduxjs/toolkit'
import counterReducer from './reducers/counterSlice'
import logger from 'redux-logger'
/*
Store : state, reducer, 내장함수 등을 관리하는 역할
하나의 어플리케이션에 하나의 Store만 생성
configureStore() : store를 생성하는 함수
React middleware : action과 reducer 사이에 특정 함수를 실행하는 중간 처리기
*/
export default configureStore({
reducer : {
counter : counterReducer
},
middleware : (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
})
configureStore 함수로 store를 생성한다. 그리고 store를 내보내서 컴포넌트에서 reducer를 사용할 수 있게 한다. reducer에 저장되는 함수들은 redux 폴더 내부의 reducer 폴더에 파일로 함수를 만든다. import를 보면 reducer에서 함수파일을 불러와서 사용한다. 어떤 이름으로 사용할지는 사용자 마음대로 정해서 사용하고 configureStore 함수에서 reducer의 속성값으로 사용하면 된다.
// counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
/*
createSlice() : state, reducer 함수를 정의하는 함수
- state 초기화
- state를 변경하는 함수 정의 -> reducer
*/
export const counterSlice = createSlice({
name : 'counter',
initialState : { // state 초기화
count : 0,
},
reducers : { // 함수 정의시, 매개변수에 반드시 state 정의
increment : (state) => { // 정의한 state는 내부에 있는 initialState를 의미한다.
state.count += 1 // 객체 접근 방식 원래는 Action을 줘야하지만 toolkit에서는 안해도 된다.
},
decrement : (state) => {
state.count -= 1
},
// 넘겨받은 숫자를 이용해서 state를 변경하는 함수 정의하기
// Action -> {type, payload} 형태로 변환, 외부의 값을 받아서 사용하기 위함
// type : 명령 타입 ex) 숫자를 증가해라 (increment) , 숫자를 감소해라 (decrement)
// payload : 외부로부터 넘겨받은 데이터를 저장하는 속성
incrementByAmount : (state, action) => {
console.log("counterSlice action", action);
// action -> {type: 'counter/incrementByAmount', payload:{num : 2}}
state.count += action.payload.num
}
}
})
// 컴포넌트에서 reducer함수를 실행할 수 있게 내보내기
// export const {increment, decrement, incrementByAmount} = counterSlice.actions
export const CounterReducerActions = counterSlice.actions
// store에서 접근할 수 있도록 내보내기
export default counterSlice.reducer
import로 redux를 불러와서 사용할 이름을 정하고 (createSlice) 해당 함수는 3개의 키값을 갖는 객체를 매개변수로 받는다. 객체의 키는 name, initialState, reducers이다. name은 그냥 이 함수 그룹의 이름이라고 생각하고, initialState는 상태, 변수의 초기값이다. reducers가 상태를 변경하는 함수들의 그룹인데, 여러 가지가 들어갈 수 있다. 하지만 해당 state과 관련된 함수를 작성해야 한다. 함수 정의 시 반드시 매개변수로 state를 받아야 한다.
initialState : { // state 초기화
count : 0,
},
매개변수의 state는 위 코드블록의 initialState 객체를 의미한다. 우리는 count의 상태를 변화시키고 싶으니까 count에 접근하기 위해서 state.count로 접근해서 값을 적절하게 변경하게 만들면 된다. 저 함수를 컴포넌트로 가져와서 실행시키면 redeucer 파일에 있는 함수가 실행되면서 상태를 변화시키는 것이다. 그리고 만든 함수들을 컴포넌트에서 사용하기 위해서 export로 내보낼 때 주석처리된 부분처럼 하면 함수가 추가되면 될수록 코드가 길어지고 더러워지기 때문에 하나의 변수에 담아서 보관한다. 그럼 이제 dispatch로 요청을 보내서 진짜 저 함수가 실행되는 것을 보자.
// 컴포넌트 Counter.jsx
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
// import { decrement, increment, incrementByAmount } from '../redux/reducers/counterSlice'
import {CounterReducerActions} from '../redux/reducers/counterSlice'
/*
1. useSelector() : store에 등록된 state를 접근하기 위한 함수
사용법 : useSelector((state) => state.등록된 reducer 속성명.state명)
등록된 reducer 속성명 -> store의 reducer에 정의된 속성
2. useDispatch() : state 변경에 대한 요청을 보내는 함수
사용법)
const dispatch = useDispatch()
dispatch(action) -> action에 대해 실행해줘
3. Action : state를 변경하기 위한 명령
ex) counterSlice의 reducers 속성에 정의된 increment, decrement
*/
const Counter = () => {
// state : store의 reducer에 접근
// counter : store의 reducer의 속성 접근
// count : reducer 속성에 접근했으니 속성값으로 접근 되서 결국 counterSlice의 state로 접근
const count = useSelector((state) => state.counter.count)
const dispatch = useDispatch()
console.log("counterReducer state", count);
return (
<div>
<h1>Redux Toolkit 활용 실습</h1>
<h2>{count}</h2>
{/* onClick(() => dispatch(명령함수 = actions)) */}
<button onClick={() => dispatch(CounterReducerActions.increment())}>+ 1</button>
<button onClick={() => dispatch(CounterReducerActions.decrement())}>- 1</button>
<button onClick={() => dispatch(CounterReducerActions.incrementByAmount({num : 2}))}>+ 2</button>
</div>
)
}
export default Counter
각 버튼을 누르면 버튼의 글자에 맞게 0의 값이 변한다. 즉, 버튼을 누르면 dispatch함수가 실행된다는 것이다. dispatch를 사용하기 위해서 const dispatch = useDispatch( )를 사용하면 된다. 그리고 버튼에 onClick 속성으로 dispatch 함수를 사용해서 실행시킬 reducer를 골라주면 된다. 실행시킬 reducer를 고르는 방법은 아까 CounterReducerActions에 reducer 함수들을 객체형태로 보관했다. 그럼 객체 key 접근 방법으로 함수 이름이 key니까 똑같이 함수 실행까지 ( )로 해주면 된다.
코드를 보면 +2하는 부분에만 함수에 매개변수로 {num : 2}를 줬다. 우리가 dispatch로 요청을 할 때 action으로 reducer함수가 실행되는 것이다. action은 payload와 type을 key로 갖는 객체이다.
payload는 외부로부터 넘겨받는 데이터이다. 무슨 말이냐면 지금 컴포넌트에서 매개변수로 함수에서 사용하는 것이다. 컴포넌트가 외부이니까 외부에서 값을 받거나 정해서 함수에서 사용할 때 action 객체의 payload로 넘어간다는 소리다. 우리는 2라는 숫자를 num이라는 key에 담아서 reducer함수에서 사용할 것이다.
type은 명령을 구분하는 것이다. 콘솔의 type을 설명하면 'counter라는 이름을 가진 함수 그룹 안 incrementByAmount함수를 사용시켜라!'라는 의미이다. '그래서 payload로 보낸 값을 저 함수에서 어떻게 사용하는데?'라는 생각이 들게 뻔하니까 설명하도록 하겠다.
// 저거 사용하는 함수
incrementByAmount : (state, action) => {
console.log("counterSlice action", action);
// action -> {type: 'counter/incrementByAmount', payload:{num : 2}}
state.count += action.payload.num
}
다른 함수들과 달리 action을 추가로 매개변수로 받고있다. 우리는 payload의 값인 객체 안의 값을 사용해야 한다. payload는 action 객체 안에 있으니 action.payload로 일단 payload까지 접근한다. payload안에 우리가 사용할 숫자 2는 num이라는 key에 보관되어 있으니 action.payload.num으로 접근하면 우리가 원하던 숫자 2를 저 함수에서 사용할 수 있게 된 것이다. 숫자 2로 상태를 변화시키는 방법은 아까와 같다.
3. redux - logger
상태 변화를 action이 발생하기 전과 후를 보고 어떤 요청이 보내지고 어떤 함수가 실행되며 어떤 결과를 나타내는지 확인하는 방법은 redux-logger를 사용하는 것이다. 지금 딱 봐도 redux 과정이 복잡하고, 여기로 갔다가 저리로 갔다가 정신이 없는데 redux - logger를 사용하면 실행 전과 후를 보여주고 어떤 action이 왔는지까지 보여주기 때문에 흐름을 알 수 있다.
'Front-End > React.js' 카테고리의 다른 글
[React Node.js] React와 Node 연결 (3) | 2023.09.18 |
---|---|
[React.js] react-router (useParams, useSearchParams) (2) | 2023.09.12 |
[React.js] Life Cycle / useEffect (0) | 2023.09.05 |
[React.js] Context (0) | 2023.09.03 |
[React.js] useRef 설명 및 실습 (0) | 2023.08.30 |