[리액트] 내가 이해하려고 적는 Redux 라이브러리
Redux❓
리덕스는 자바스크립트 앱의 상태를 관리하기 위한 전역 상태 관리 라이브러리이다.
Redux를 사용하는 이유❓
리덕스를 사용하면 상태값을, 컴포넌트에 종속시키지 않고, 상태 관리를 컴포넌트의 바깥에서 관리 할 수 있게 된다.
👾 useState의 불편함
- 컴포넌트에서 컴포넌트로 State를 보내기위해서는 반드시 부-모 관계여야한다.
- State를 전달해주기만 하는 List 컴포넌트에도 불필요하게 State 값을 내려주어야한다.
- 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없다.
- 자식이 많아진다면 상태 관리가 매우 복잡해진다
- 상태를 관리하는 상위 컴포넌트에서 계속 내려 받아야한다. (Props drilling 이슈)
✨ 리덕스를 통해 해결가능한 점
1. 전역 상태 저장소 제공
2. Props drilling 이슈 해결
✏️ 내가 이해한대로 그리는 리덕스 구조
간단한 로직이다. view에서 상태 변화가 생겼다면 dispatch 메서드에 액션 객체(데이터)를 담아 reducer를 호출해준다. dispatch에 전달인자로 담긴 action을 reducer에서는 매개변수로 받아 실행한다. 그리고 액션의 타입에 따라 상태를 업데이트 해주고 store에 변경된 최신 상태를 제공한다. 마지막으로 useSelector 를 통해 store에서 초기/최신 상태를 받아 UI 를 렌더링한다.
📌 Action : { type, payload } | 상태 변화를 일으키는 객체이다. type은 필수 속성이고 payload는 선택 속성이다. 추가적인 데이터를 받을 때 사용한다. |
📌 Dispatch | 액션 객체를 전달인자로 받아 리듀서를 호출하는 함수이다. 리듀서 함수는 dispatch를 통해 액션 객체를 매개변수로 받는다. |
📌 Reducer | 기존 상태와 액션 객체를 매개변수로 가지며 이 두가지를 참조하여 상태를 변경한다. 액션 객체에서 action.type에 따라 상태를 어떻게 변경할지 정의하는 함수이다. 상태 변경 후 최신 상태를 Store에게 제공한다. |
1. 스토어 생성 및 스토어 구독
B 컴포넌트에서 일어난 상태 변화가 G 컴포넌트에 반영된다고 가정해보자.
스토어를 생성한 후에 G 컴포넌트에서 useSelector 로 store을 구독하고 state의 최신상태를 받는다.
useSelector((state) => state.구독대상)
2. 상태 변경 요청
B 컴포넌트에서 상태를 업데이트할 일이 생긴다. 이 때 dispatch라는 함수에 액션을 담아 store에 상태 업데이트를 요청한다.
dispatch 함수에 담은 액션을 전달인자라고 하는데 이는 리듀서를 호출할 때 필요한 매개변수로 사용한다.
3. 리듀서로 상태 업데이트
B 컴포넌트로부터 전달받은 action 객체의 타입에 따라 상태를 업테이트 해줘야한다. 이때 액션의 타입에 따라 어떻게 상태를 업데이트 해주는지 정의하는 함수를 리듀서 함수라고 한다. 아래 두가지 매개변수를 참조하여, 새로운 상태 객체를 만들어서 최신 상태를 반환한다.
📌 reducer 함수는 두가지 매개변수를 갖는다.
- state : 현재 상태
- action : 액션 객체
4. 구독 알림
위 과정을 다 거치면 컴포넌트는 새로운 상태를 받게되고, 이에 따라 컴포넌트는 리렌더링한다.
Redux 설치
yarn add redux react-redux
파일 생성
📂 redux : 리덕스와 관련된 코드를 모두 모아 놓을 폴더
📂 config : 리덕스 설정과 관련된 파일들을 놓을 폴더
📂 cofigStore.js : 중앙 state 관리소
📂 module : State들의 그룹 폴더
📂 text.js :하나의 모듈
Store 생성
combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만든다.
import { combineReducers, createStore } from "@reduxjs/toolkit";
import texts from "../module/text";
// 1. combineReducers로 리듀서 조합
const rootReducer = combineReducers({ texts });
조합한 리듀서로 스토어를 생성한다.
import { combineReducers, createStore } from "@reduxjs/toolkit";
import texts from "../module/text";
// 1. combineReducers로 리듀서 조합
const rootReducer = combineReducers({ texts });
// 2. 조합한 리듀서들로 스토어 생성
const store = createStore(rootReducer);
export default store;
리듀서 생성
texts라는 리듀서에 onAddText라는 액션 타입을 만들어준다. 이때 texts(리듀서)는 state와 action 두개의 매개변수를 갖는다. 두개의 매개변수를 참조하여 상태를 업데이트한다. 아래 코드를 보면 초기값은 로컬스토리지에 저장되어있는 데이터가 있다면 불러온 데이터가 초기값이 되고 없다면 빈 배열이 초기값이 된다.
액션 타입 onAddText인 경우 처리는 다음과 같다. 기존 상태(...state)를 복사하여 변경하고, text 속성에 새로운 배열을 할당한다. 기존 text 배열과 새로운 항목(action.payload)을 합쳐서 새로운 배열을 생성한다.
// 초기값 설정
const initialState = {
text: localStorage.getItem("texts") || [],
};
console.log(initialState);
const texts = (state = initialState, action) => {
switch (action.type) {
case "onAddText":
return {
...state,
text: [...state.text, action.payload],
};
default:
return state;
}
};
export default texts;
리스트를 추가할 때 input창에 입력하는 값이 출력되어야한다. 리듀서 함수에서 만든 액션 타입을 dispatch 메서드를 사용해서 스토어에 상태 변경을 요청하는 방법은 아래와 같다.
import React, { useState } from "react";
import { useDispatch } from "react-redux";
function TextInput() {
const dispatch = useDispatch();
const [inputValue, setInputValue] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (inputValue.trim()) {
dispatch({ type: "onAddText", payload: inputValue });
setInputValue("");
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="Enter text"
/>
<button type="submit">Add</button>
</form>
);
}
export default TextInput;