Redux관련 포스팅은 많지만, 너무 어렵다..
이 포스팅 하나로 최대한 간단하지만 최대한 많이 알리고싶은게 목적이다.
1. Redux는 왜 쓰는가?
- useState가 불편해서....
2. Redux vs Redux Toolkit
- 항해 7기는 React Redux만 배운다
- Reduxjs Toolkit 은 React Redux를 보다 쉽고 짧고 편하게 쓰기 위해 나왔다. 실제로 코드량이 반토막난다.
- 벨로퍼트님도 Redux Toolkit을 꼭 사용하자고 하신다.
- 하지만 항해에서 안배우는거라 툴킷 잘못쓰면 다른 팀원들이 못알아보고 Redux를 사용 못하게 되는 불상사가 일어날지도
아래 마치면서 출처: https://ridicorp.com/story/how-to-use-redux-in-ridi/
3. Redux vs Context API
전역으로 데이터를 관리하는애는 Redux말고 Context API도 있는데 왜 Redux를 배울까?
- Redux에는 미들웨어(middleware)가 있다.
- 우리가 변경하고싶은 값을 바로 변경시키지 않고, 그 값을 사용해 다른 걸 할수 있다.
- 예를들어 Greeting 이라는 함수에서 hi 를 return해 주는데,
이걸 바로 return하지않고 hi 한다음에 nice meet you의 행동 까지 추가할수 있단 말.
- 비동기 작업처리가 가능하다 라고 기억하면 된다.
- 비슷한 Hooks 중에 useReducer가 있는데 몰라도된다 일단은.
4. 그러면 Redux를 언제 쓰는게 좋을까?
- 프로젝트 규모가 크거나, 비동기 작업이 많을때 쓰면 좋다.
- 단순히 전역변수들 상태관리할때는 뭐든 상관없을거 같다.
5. 기본 구조 (이론) - React Redux
- Redux나 툴킷이나 일단 기본 구조 이론편은 똑같다.
- Store는 상태가 관리되는 공간이다. 쉽게말해서 변수저장은 여기다 하면된다
- 프로젝트내에 단 한개만 존재해야한다.
- Reducer는 store에 저장되어있는 상태를 직접적으로 수정해주는 애다.
- 쉽게말해서, CRUD가 일어나면, 얘를 통해서 store에 저장된 값을 업데이트 해야한다.
- CRUD가 일어나면 값을 수정해야하기때문에 취해진 행동(action)과, 수정할 값(state)을 파라미터로 받아와야한다(must)
function counterReducer(state, action) {
switch (action.type) {
case 'INCREASE':
return state + 1;
case 'DECREASE':
return state - 1;
default:
return state;
}
}
- Action은 상태에 어떤 변화가 필요하게 될때 일어난다.
- 이런 액션들은 객체로 표현되어있고, type이라는 필드를 무조건 가지고 있어야한다
{
type: "ADD_TODO",
data: {
id: 0,
text: "리덕스 언제끝내냐.."
}
}
- 이런 액션들은 보통 함수로 관리한다. 1액션 1함수 기억하자.
export function addTodo(data) {
return {
type: "ADD_TODO",
data: {
id: 0,
text: "리덕스 언제끝내냐.."
}
};
}
- View는 그냥 유저들이 보는 모니터 즉, 브라우저다.
쉽게말해서 값이 변경되면 - action을 dispatch했다 라고 외우면된다.
변경된 값은 reducer가 처리해서 store한테 전달
store는 변경된 값을 필요한 애들한테 업데이트해준다.
업데이트는 자동으로 이루어진다.
우리가 유튜브 구독하면 유튜버들이 뭐 올리면 자동으로 알림오듯이, 리듀서도 구독한애들은 자동으로 업데이트해준다.
- 여기서 이용하는 Hook이 useSelector이다. (아래서 설명예정)
5. 기본 구조 (실전) - React Redux
벨로퍼트님이 툴킷 하라해도 이것도 해야한다.
React Redux를 사용해서 to do 를 추가하는걸 만들어 보려고 한다.(리덕스 CRUD 기초 틀 복붙할수있는 사이트 - https://github.com/erikras/ducks-modular-redux)
폴더구조에 정답은 없겠지만, src에 난사하는건 보고있을수 없으니 꼭 redux 폴더를 만들자.
components - 일반 컴포넌트들 보관하는곳
containers - 리덕스 store의 상태를 조회하거나 action을 dispatch 할수있는 컴포넌트
pages - routing 해줄 페이지들
modules - reducer, action함수들이 있다. index.js는 여러개의 reducer를 하나로 합쳐주는 combineReducer가 있다.
configStore.js - 나는 store를 여기서 생성해준다. 하지만 modules의 index.js나 밖의 index.js에서 해줘도 될거같다.
설치
npm install react-redux redux
최상단 index.js 는 이 구조 외엔 없다.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// Provider를 사용하기 위한 import
import { Provider } from 'react-redux';
// store 를 곧 생성할 예정
import store from './redux/configStore'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// 내가 사용하고싶은 전역상태관리의 범위만큼 Provider로 감싸고
// 전역상태의 데이터가 들어있는 store를 이렇게 넘겨준다
<Provider store={store}>
<App />
</Provider>
);
이제 store를 생성해준다
./redux/configStore.js
import { createStore } from "redux";
// 모듈안에서 rootReducer 가져와서 store를 생성 예정
import rootReducer from './modules';
// 생성!
const store = createStore(rootReducer);
export default store;
이 store 생성은 최상단 index.js에서 해줘도 된다. 실제로 벨로퍼트님은 최상단 index.js에서 하셨다.
아래 사진은 createStore를 쓰면 툴킷에 있는 configureStore을 쓰라고 권한다.
다음은 rootReducer를 생성한다.
combineReducers - reducer들을 합칠수 있다.reducer가 한개면 그 reducer를 이용해서 바로 store를 만들어도 된다../redux/modules/index.js
import { combineReducers } from 'redux';
import counter from './counter';
// counter, todo reducer들을 이렇게 합친다.
const rootReducer = combineReducers({
counter,
// 여기에는 새롭게 추가하고싶은 reducer를 가져온다
});
export default rootReducer;
counter reducer를 만들어 줄 파일
./redux/modules/counter.js
/* 액션 타입 만들기 */
// 다른모듈과 이름 중복방지를 위해 이렇게 만든다
const SET_DIFF = 'counter/SET_DIFF';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
/* 액션 생성함수 만들기 */
// 내가 밖에서 사용할 액션함수를 만든다. export빼먹지말기
export const setDiff = diff => ({ type: SET_DIFF, diff });
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
/* 초기 상태 선언 */
// 제일 처음 store가 가질 초기값이다
const initialState = {
number: 0, // 화면에 보여줄 초기값
diff: 1 // input에 띄워줄 초기값
};
/* 리듀서 선언 */
// 리듀서는 합치는애가 받아야하니 export를 잊지말자
// 파라미터에는 초기값이랑 어떤액션이 취해져야하는지가 들어간다.
export default function counter(state = initialState, action) {
// action은 기본적으로 type이있다. 그걸로 switch case
switch (action.type) {
// input에 띄워줄 값
case SET_DIFF:
// state안의 객체에있는 diff key를 가진놈한테 데이터를 덮어씌운다
return {...state, diff: action.diff};
case INCREASE:
return { ...state, number: state.number + state.diff};
case DECREASE:
return {...state, number: state.number - state.diff};
default:
return state;
}
}
CounterContainer 는 리덕스 스토어의 상태를 조회하거나, 액션을 디스패치 할 수 있는 컴포넌트를 의미.
./containers/CounterContainer.js
import React from 'react';
// 값을 수정하기 위해 import
import { useSelector, useDispatch } from 'react-redux';
import Counter from '../components/counter';
// counter reducer에서 가져온 액션함수
import { increase, decrease, setDiff } from '../redux/modules/counter';
function CounterContainer() {
// useSelector는 리덕스 스토어의 상태를 조회하는 Hook
// 비구조화로 객체의 경우 좌항과 우항이 매칭된다.
const { number, diff } = useSelector(state => ({
number: state.counter.number,
diff: state.counter.diff
}));
//-> 그냥 CRUD할땐 무조건 dispatch를 써야한다, 그래서 선언
const dispatch = useDispatch();
// 함수를 만드는데, 그 함수를 dispatch를 이용해서 reducer에서 만든애들을 불러와야한다.
const onIncrease = () => dispatch(increase());
const onDecrease = () => dispatch(decrease());
const onSetDiff = diff => dispatch(setDiff(diff));
return (
<Counter
// 상태와
number={number}
diff={diff}
// 액션을 디스패치 하는 함수들을 props로 넣어줍니다.
onIncrease={onIncrease}
onDecrease={onDecrease}
onSetDiff={onSetDiff}
/>
);
}
export default CounterContainer;
./components/counter.js
import React from 'react';
// props로 넘어온애들을 일단 다 써줌
function Counter({ number, diff, onIncrease, onDecrease, onSetDiff }) {
// 버튼이벤트
const onChange = e => {
// e.target.value 의 타입은 문자열이기 때문에 숫자로 변환해주어야 합니다.
// parseInt(값,진법)
onSetDiff(parseInt(e.target.value, 10));
};
return (
<div>
// 보여줄 숫자
<h1>{number}</h1>
<div>
// input창에 띄워줄 숫자, diff의 값을 가져와서 보여준다. min은 없어도 되지만,
// 혹시모를 상황에 대비해서 넣으신거 같다(최소값)
<input type="number" value={diff} min="1" onChange={onChange} />
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
</div>
);
}
export default Counter;
App.js
import React from "react";
import CounterContainer from './containers/CounterContainer';
function App() {
return (
<>
<div>
<CounterContainer />
</div>
</>
);
}
export default App;
- CounterContainer를 만들지 않고 그냥 Counter.js 컴포넌트 안에서 CRUD과정이 이루어 져도 되지만,컴포넌트를 두개로 나눠서 관리하는 이유는 재사용성 때문이라고 한다.
'Web > React' 카테고리의 다른 글
React Redux - 새로고침 시에도 리덕스 내의 데이터를 유지하려면 어떤 방법을 써야할까요? (0) | 2022.05.31 |
---|---|
React - 유니크 아이디 생성 (0) | 2022.05.28 |
명령형 vs 선언형 프로그래밍 (0) | 2022.05.27 |
React - Redux & Middleware (0) | 2022.05.27 |
React - Redux Toolkit (0) | 2022.05.26 |