본문 바로가기

POSCO x CODINGOn 웹개발자 양성 부트캠프

[포스코x코딩온] 웹개발자 풀스택 과정 14주차 회고 | React: [Redux]

React Redux 적용

npm install redux react-redux @reduxjs/toolkit

 

@reduxjs/toolkit

  • Redux의 복잡성을 줄이기 위해 만들어진 도구
  • action 생성, reducer, middleware 등 Redux와 관련된 기능들을 효율적으로 구현할 수 있다.

<Provider>

  • 최상위 component에서 하위 component들에 store를 전달해 준다.
  • store이라는 props에 import 한 store를 넣어준다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import {Provider} from 'react-redux';
import store from './store';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
	<React.StrictMode>
    	<Provider store={store}>
			<App />
		</Provider>
    </React.StrictMode>
);

 

useSelector()

  • 하위 component에서 <Provider>로 전달받은 store의 state값을 조회하기 위한 hook 함수
  • 인자로 콜백을 받고 해당 콜백은 state를 매개변수로 받을 수 있고, 원하는 state의 값만 추출해 반환할 수 있다.

useDispatch()

  • action을 발생시키는 dispatch 함수를 실행하는 hook 함수
  • 인자로 원하는 ㅊction 객체를 넘겨줘야 한다.

 

import {useSelector, useDispatch} from 'react-redux';

//useSelector()를 이용하여 state 객체의 number키의 값을 반환받는다.
const number = useSelector(state => state.number);

//useDispatch()를 이용하여 action객체를 넣고 해당 action을 발생시킬수 있는 함수를 반환받는다.
const dispatch = useDispatch();

//사용 예제
<button onClick={()=>{dispatch({ type: 'INCREASE' })}}>+1</button>

Redux Toolkit (@reduxjs/toolkit)

 

configureStore()

  •  createStore() 함수보다 더욱 편리한 방법으로 Redux store을 생성하는 함수
  • 여러 middleware와 reducer를 쉽게 통합할 수 있다

createSlice()

  • reducer와 action을 함께 생성하는 함수
  • 슬라이스라는 개념을 사용하여 action type, action 생성 함수, reducer를 한 번에 정의한다.

 

store 생성 & reducer 및 action 정의하기

// createSlice, configureStore 함수 불러오기
import {createSlice, configureStore} from '@reduxjs/toolkit';


// createSlice 안에 들어가는 객체의 속성들은 name, initialState, reducer로 고정
// 이때 createSlice 함수로 반환된 객체는 reducer와 actions 속성이 포함되어있어 store 생성할때 .reducer로 접근하고
// action을 내보낼때 .actions로 접근하여 내보낼수 있다.

const initialCounterState = {counter: 0}
const counterSlice = createSlice({
	name: 'counter',
    initialState: initialCounterState,
    reducers: {
    	//reducer속성안에 action 함수들을 미리 정의해준다.
        //이때 action 함수들을 현재 상태를 가지고 있는 state와 action을 받을 수 있고, 추가적인 데이터는 무조건 action.payload로 받는다 
		increment(state) {
			state.counter++;
        },
        decrement(state) {
			state.counter--;
        },
        calculate(state, action) {
			state.counter = state.counter + action.payload;
        },
    },
});


const initialAuthState = {isLogin: false}
const authSlice = createSlice({
	name: 'auth',
    initialState: initialAuthState,
    reducers: {
		login(state) {
			state.isLogin = true;
        },
        logout(state) {
			state.isLogin = false;
        },
    }.
});
    
//configureStore 안에 사용할 reducer들을 객체로 한번에 정의할 수 있다.
const store = configureStore({
	reducer: { counter: counterSlice.reducer, auth: authSlice.reducer },
});

// action들을 내보낼때
export const counterActions = counterSlice.actions;
export const authActions = authSlice.actions;

export default store;

 

하위 component들에게 store  전달하기

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './store';

// Provider component에 store props로 configureStore()함수로 생성된 store를 넣어준다
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
	<React.StrictMode>
		<Provider store={store}>
			<App />
		</Provider>
	</React.StrictMode>
);

 

하위 component에서 store 접근하기

//useSelector(), useDispatch() 불러오기
import { useSelector, useDispatch } from 'react-redux';

//action들 불러오기
import { counterAction, counterAction } from './store/counter';

//component 생성
export default function Counter() {

	//useSelector를 이용하여 state에서 필요한 값만 추출하고 할당
	const counter = useSelector((state) => state.count.counter);
    const login = useSelector((state) => state.login.isLogin);
    
    //만든 dispatch 함수의 인자로 import 한 action객체의 특정 action 함수를 넣어준다
	const dispatch = useDispatch();

	const add = () => {
		dispatch(counterAction.increment());
	};
	const minus = () => {
		dispatch(counterAction.decrement());
	};
    //counterAction.calc() 안에 들어가는 인자는 reducer안에 정의된 calc함수에서 action.payload로 접근가능
	const calc = () => {
		dispatch(counterAction.calc(5));
	};

	return (
		<div>
			<h2>{counter}</h2>
			<button onClick={add}>ADD</button>
			<button onClick={minus}>SUBTRACT</button>
			<button onClick={calc}>CALC</button>
            {login ? (
				<>
					<div>로그인 하셨습니다.</div>
					<button onClick={() => dispatch(loginAction.logout())}>로그아웃</button>
				</>
			) : (
				<>
					<div>로그아웃 하셨습니다.</div>
					<button onClick={() => dispatch(loginAction.login())}>로그인</button>
				</>
			)}
		</div>
	);
}

 


 느낀 점

프로젝트 규모가 커질수록 더욱 복잡한 로직을 처리하고 방대한 데이터를 전역적으로 제공하려면 상태의 개수가 엄청 많아지는 문제가 발생하는데, @reduxjs/toolkit가 제공하는 함수들을 사용하여 특정 상태만 다루는 reducer와 action들을 각각 정의하고 사용함으로써 해당 문제를 해결할 수 있을 것 같습니다. (페이지의 테마 상태, 사용자 로그인 상태 등).