redux
를 이용하여 전역에서 관리하고자 한다.redux
에 대한 이해가 부족해 바로 프로젝트에 적용시키는 것 보단 간단한 카운터 앱을 만들어보는 것으로 포문을 열기로 했다.redux
만 단독으로 설치하는 것 보단 redux-toolkit(RTK)
을 권장하여, RTK
를 설치하여 진행했다.RTK
을 설치하기는 하지만, 기존 redux
문법을 사용하여 작성한다.기존의 프로젝트에 적용하기
npm i react-redux @reduxjs/toolkit
# 바닐라 redux 가 toolkit에 포함되어 있어 따로 설치할 필요가 없다.
새 프로젝트를 만들어 create-react-app 의 template 사용하기.
npx create-react-app [파일경로] --template redux
# JS 환경이라면 위의 명령어로 설정가능
npx create-react-app [파일경로] --template redux-typescript
# TS 환경이라면 위의 명령어로 설정가능
→ CRA의 템플릿을 이용할 경우 react-redux
, @reduxjs/toolkit
라이브러리가 기본적으로 설치되며, 간단한 예제 코드가 App 컴포넌트에 작성되어 있다.
store
, reducer
, action
으로 나뉘며 각각의 역할은 아래와 같다.store
에 상태를 객체 형식으로 저장해두고, 필요한 경우 store
를 통해 해당 상태를 가져 올 수 있다.reducer
에게 어떤 행동을 할지 전달하는 역할을 한다.reducer
는 action
의 type
을 기준으로 상태를 변화시키는 로직을 수행하게 된다.action
을 기반으로 새로운 값을 만들어 상태를 갱신하는 역할을 한다.if
혹은 switch-case
문법을 이용하여 action
의 type
에 따라 상태를 변화시키는 로직을 실행한다.// 파일 구조
|- src
|- App.tsx
|- redux
|- store.ts
|- counter
|- actions.ts
|- reducer.ts
|- types.ts
//actions.ts
const plusCounter = () => {
return {
type: "PLUS_COUNTER"
}
}
const minusCounter = () => {
return {
type : "MINUS_COUNTER"
}
}
const clearCounter = () => {
return {
type : "CLEAR_COUNTER"
}
}
action
을 만들어 주었다.type
들이 actions
뿐만 아니라 reducer
에도 사용되기 때문에 재사용성을 위해 type
을 따로 파일로 분리하고, 변수로 관리한다.// types.ts
export const PLUS_COUNTER = 'PLUS_COUNTER';
export const MINUS_COUNTER = 'MINUS_COUNTER';
export const CLEAR_COUNTER = 'CLEAR_COUNTER';
// actions.ts
import { PLUS_COUNTER, MINUS_COUNTER, CLEAR_COUNTER } from './types';
export const plusCounter = () => {
return {
type: PLUS_COUNTER,
};
};
export const minusCounter = () => {
return {
type: MINUS_COUNTER,
};
};
export const clearCounter = () => {
return {
type: CLEAR_COUNTER,
};
};
actions
또한 외부에서 사용되어야 하므로, 각 action
에 export
키워드를 붙혀준다.types
를 import
해주고, 상태의 초기값을 initialState
에 객체형식으로 지정해주고, reducer
를 작성한다.import { PLUS_COUNTER, MINUS_COUNTER, CLEAR_COUNTER } from './types';
const initialState = {
count : 0
};
const counterReducer = (state = initialState, action : {type : string}) => {};
state = initialState
문법은, 해당 위치에 인자가 들어오지 않은 경우 초기값을 해당 값으로 가진다 라는 뜻이다.switch-case
문을 이용하여 action
의 타입이 일치하는 경우 return
내부에 있는 로직을 실행 할 수 있게끔 한다.import { PLUS_COUNTER, MINUS_COUNTER, CLEAR_COUNTER } from './types';
const initialState = {
count : 0
}
const counterReducer => (state = initialState, action : {type : string}) => {
switch(action.type){
case PLUS_COUNTER :
return {
...state,
count : state.count + 1
}
case MINUS_COUNTER :
return {
...state,
count : state.count - 1
}
case CLEAR_COUNTER :
return {
...state,
count : 0
}
default:
return state
}
}
export default counterReducer
case
에서 state
를 스프레드 문법으로 가져오는데, 이는 상태를 직접 변경하지 않는다는 원칙 때문에 해당 객체를 복사하여 값을 변경하는 것이다.initialState
의 값을 직접 변경하는 것이 아니라, 전체를 복사해 불러오고 원하는 값만 변경시켜 상태를 갱신하는 것이라고 볼 수 있겠다.store
를 만들기 위해서는 reducer
가 인자로 들어가야한다.action
, reducer
들이 만들어졌으니, store
를 만들고 프로젝트에 연결하자.import { legacy_createStore as createStore } from '@reduxjs/toolkit';
import counterReducer from './counter/reducer';
const store = createStore(counterReducer);
export default store;
// 내보내어 사용한다.
→ RTK 를 사용하는 경우 configureStore
를 사용하지만, 지금 프로젝트에서는 기존의 redux 의 core 기능만 사용해 볼 것이기 때문에 위와 같이 legacy_createStore
를 사용한다.
index.tsx
에서 에서 react-redux
의 Provider
를 이용하여 감싸주고, 사용할 store
를 props
로 전달한다.import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './redux/store';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<App />
</Provider>
);
connect
함수와 mapStateToProps
, mapDispatchToProps
함수
import React from 'react';
import './App.css';
import { connect } from 'react-redux';
import { plusCounter } from './redux/counter/actions';
function App(props: { count: number; plusCounter: any }) { // 타입지정
return (
<div className="App">
<div>{props.count}</div>
<button onClick={() => props.plusCounter()}>+</button>
</div>
);
}
const mapStateToProps = (state: { count: number }) => {
return {
count: state.count,
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
plusCounter: () => dispatch(plusCounter()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
mapStateToProps
는 store에 저장된 상태를 props로 전달하며, connect 함수의 첫번째 인자로 사용된다.mapDispatchToProps
는 reducer 로 action 을 전달하는 함수를 props 로 전달되며, connect 함수의 두번째 인자로 사용된다.any
를 사용해 벗어났다.useSelector
, useDispatch
Hook 사용하기
import React from 'react';
import './App.css';
import { useSelector, useDispatch } from 'react-redux';
import { plusCounter } from './redux/counter/actions';
function App() {
const state = useSelector((state: { count: number }) => state);
const dispatch = useDispatch();
return (
<div className="App">
<div>{state.count}</div>
<button onClick={() => dispatch(plusCounter())}>+</button>
</div>
);
}
export default App;
useSelector
는 store
에 저장된 상태를 반환하여 해당 변수에 저장한다.useDispatch
는 함수를 변수에 지정하여 사용하고, 그 인자로 action
이 사용된다.