props와 state를 관리를 할 수있다.
리덕스는 여러 컴포넌트가 동일한 상태를 보고 있을 때 굉장히 유용합니다!
또, 데이터를 관리하는 로직을 컴포넌트에서 빼면, 컴포넌트는 정말 뷰만 관리할 수 있어서코드가 깔끔해질테니, 유지보수에도 아주 좋다.
상태관리 흐름도
Store, Action, Reducer, 그리고 Component
yarn add redux react-redux
설치
리덕스는 데이터를 한 군데 몰아넣고, 여기저기에서 꺼내볼 수 있게 해준다.
State
리덕스에서는 저장하고 있는 상태값("데이터"라고 생각하셔도 돼요!)를 state라고 불러요.
딕셔너리 형태({[key]: value})형태로 보관
Action
상태에 변화가 필요할 때(=가지고 있는 데이터를 변경할 때) 발생하는 것입니다
{type: 'CHANGE_STATE', data: {...}}
요런식으로 쓴다.
ActionCreator
액션 생성 함수라고도 부릅니다. 액션을 만들기 위해 사용합니다.
//이름 그대로 함수예요!
const changeState = (new_data) => {
// 액션을 리턴합니다! (액션 생성 함수니까요. 제가 너무 당연한 이야기를 했나요? :))
return {
type: 'CHANGE_STATE',
data: new_data
}
}
Reducer
리덕스에 저장된 상태(=데이터)를 변경하는 함수입니다.
우리가 액션 생성 함수를 부르고 → 액션을 만들면 → 리듀서가 현재 상태(=데이터)와 액션 객체를 받아서 → 새로운 데이터를 만들고 → 리턴해줍니다.
// 기본 상태값을 임의로 정해줬어요.
const initialState = {
name: 'mean0'
}
function reducer(state = initialState, action) {
switch(action.type){
// action의 타입마다 케이스문을 걸어주면,
// 액션에 따라서 새로운 값을 돌려줍니다!
case CHANGE_STATE:
return {name: 'mean1'};
default:
return false;
}
}
Store
스토어에는 리듀서, 현재 애플리케이션 상태, 리덕스에서 값을 가져오고 액션을 호출하기 위한 몇 가지 내장 함수가 포함되어 있습니다.
생김새는 딕셔너리 혹은 json처럼 생겼어요
dispatch
디스패치는 우리가 앞으로 정말 많이 쓸 스토어의 내장 함수예요!
액션을 발생 시키는 역할을 합니다.
dispatch(action)
리덕스의 3가지 특징
store는 1개만 쓴다 - 한 프로젝트에 스토어는 하나만 씁니다.
store의 state(데이터)는 오직 action으로만 변경할 수 있다 - 리액트에서도 state는 setState()나, useState() 훅을 써서만 변경 가능했죠!
데이터가 마구잡이로 변하지 않도록 불변성을 유지해주기 위함입니다.
불변성 뭐냐구요? 간단해요! 허락없이 데이터가 바뀌면 안된단 소리입니다!
조금 더 그럴 듯하게 말하면, 리덕스에 저장된 데이터 = 상태 = state는 읽기 전용입니다.
그런데... 액션으로 변경을 일으킨다면서요? 리듀서에서 변한다고 했잖아요?
→ 네, 그것도 맞아요. 조금 더 정확히 해볼까요!
가지고 있던 값을 수정하지 않고, 새로운 값을 만들어서 상태를 갈아끼웁니다!
어떤 요청이 와도 리듀서는 같은 동작을 해야한다
리듀서는 순수한 함수여야 한다는 말입니다.
순수한 함수라는 건,
덕스(ducks) 구조
덕스 구조는 모양새로 묶는 대신 기능으로 묶어 작성합니다.
(버킷리스트를 예로 들자면, 버킷리스트의 action, actionCreator, reducer를 한 파일에 넣는 거예요.)
// widgets.js
// Actions
const LOAD = 'my-app/widgets/LOAD';
const CREATE = 'my-app/widgets/CREATE';
const UPDATE = 'my-app/widgets/UPDATE';
const REMOVE = 'my-app/widgets/REMOVE';
// Reducer
export default function reducer(state = {}, action = {}) {
switch (action.type) {
// do reducer stuff
default: return state;
}
}
// Action Creators
export function loadWidgets() {
return { type: LOAD };
}
export function createWidget(widget) {
return { type: CREATE, widget };
}
export function updateWidget(widget) {
return { type: UPDATE, widget };
}
export function removeWidget(widget) {
return { type: REMOVE, widget };
}
// side effects, only as applicable
// e.g. thunks, epics, etc
export function getWidget () {
return dispatch => get('/widget').then(widget => dispatch(updateWidget(widget)))
}
Store 연결하기
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
// 우리의 버킷리스트에 리덕스를 주입해줄 프로바이더를 불러옵니다!
import { Provider } from "react-redux";
// 연결할 스토어도 가지고 와요.
import store from "./redux/configStore";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
reportWebVitals();
컴포넌트에서 리덕스 액션 사용하는 법
리덕스 훅
// useDispatch는 데이터를 업데이트할 때,
// useSelector는 데이터를 가져올 때 씁니다.
import {useDispatch, useSelector} from "react-redux";
...
// redux 훅 중, useSelector를 가져옵니다.
import { useSelector } from "react-redux";
const BucketList = (props) => {
let history = useHistory();
// 이 부분은 주석처리!
// console.log(props);
// const my_lists = props.list;
> // 여기에서 state는 리덕스 스토어가 가진 전체 데이터예요.
// 우리는 그 중, bucket 안에 들어있는 list를 가져옵니다.
const my_lists = useSelector((state) => state.bucket.list);
return (
<ListStyle>
{my_lists.map((list, index) => {
return (
<ItemStyle
className="list_item"
key={index}
onClick={() => {
history.push("/detail");
}}
>
{list}
</ItemStyle>
);
})}
</ListStyle>
);
};
...
state는 리덕스 스토어가 가진 전체 데이터
//App.js
// useDispatch를 가져와요!
import {useDispatch} from "react-redux";
// 액션생성함수도 가져오고요!
import { createBucket } from "./redux/modules/bucket";
useDispatch 훅
const dispatch = useDispatch();
const addBucketList = () => {
// 스프레드 문법! 기억하고 계신가요? :)
// 원본 배열 list에 새로운 요소를 추가해주었습니다.
// 여긴 이제 주석처리!
// setList([...list, text.current.value]);
dispatch(createBucket(text.current.value));
};