리덕스(Redux)는 상태 관리 라이브러리 중 하나로, 리덕스를 통해 전역 상태 관리를 편리하게 할 수 있으며, state관련 로직들을 각각 파일로 나눠서 관리할 수 있다. 또한 컴포넌트끼리 상태를 공유하게 될 때 여러 컴포넌트를 거치지 않고도 손쉽게 상태 값을 전달 할 수 있음
리덕스를 사용하는 가장 큰 이유는 공통적으로 이용하는 상태를 전역에서 관리를 하기 위함이다.
이외 리덕스를 사용하면 컴포넌트끼리 똑같은 상태를 공유해야 할 때도 여러 컴포넌트를 거치지 않고 손쉽게 상태 값을 전달하거나 업데이트할 수 있는 등의 전달하는 과정을 단순화할 수 있고, 여러 컴포넌트에서 동일한 state를 업데이트하는 경우 비정상적인 상황을 방지할 수 있고 상태 값을 컴포넌트에 종속시키지 않고, 상태 관리를 컴포넌트의 밖에서 관리할 수 있다.
이런 장점들 덕분에 프로젝트가 커지면서 컴포넌트구조가 복잡해질 경우 리덕스를 이용하면 유지보수, 코드작성의 효율성을 극대화 해준다.
State : 리덕스에서는 저장하고 있는 상태값을 딕셔너리 형태로 보관한다.
Store : 프로젝트 안에서 리덕스의 상태를 저장하기 위해 존재하며, 리덕스에서는 한 애플리케이션 당 하나의 스토어만 있어야함. 스토어 안에는, 현재의 앱 상태와, 리듀서가 들어가있고, 추가적으로 몇가지 내장 함수들이 있습니다.
Action : 상태에 변화가 필요할 때 발생하는 것이다. 상태의 변화가 일어날 때 발생하는 어떤 것이라고 생각하면 된다.
Dispatch: 액션을 스토어에 전달하는 메서드로, dispatch를 통해서 액션함수를 호출하는 것이다.
-> useDispatch메서드로 dispatch를 생성하여 사용하면됨!
Reducer: 리덕스에 저장된 상태를 변경하는 함수이다.
UseSelector 메서드 : UseSelector메서드는 Redux-Store에 저장된 State 값을 반환한다.
Provider : 리액트 앱에 스토어를 쉽게 연결하기 위한 컴포넌트
npm install redux react-redux
먼저 Ducks패턴의 모듈을 만들건데, 먼저 Ducks패턴이란 ? 액션 타입, 액션 생성함수, 리듀서 함수를 파일 하나에 몰아서 다 작성하는 방식이며, Ducks 패턴을 사용하여 액션 타입, 액션 생성 함수, 리듀서를 작성한 코드를 '모듈' 이라고 한다.
Ducks패턴을 사용하는 이유로는 원래 리덕스는 actions, constants, reducers 이렇게 3가지로 패키지를 구분하여 사용하는데 분류가 잘되어서 찾기 편하고 유지보수가 용이하다는 장점을 갖지만 action이 추가되는 경우 세 종류의 파일들을 모두 수정해야 하므로 번거로운 단점을 가지고 있다.이런 단점을 해결하기 위한 방식이 Ducks 방식이다.
// redux/counter.js
// 액션 타입 정의
const INCREASE = "counter/INCREASE";
const SET_NUMBER = "counter/SET_NUMBER";
// 액션 생성 함수
export const increase = (count) => {
return {
type: INCREASE,
count
};
}
export function set_number() {
return {
type: SET_NUMBER
};
}
// 초기 상태 정의
const initialState = { count: 0 };
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
case 'INCREASE':
return {
count: ++state.count
};
case 'SET_NUMBER':
return {
count: action.numberValue
};
default:
return state;
}
};
export default counter;
//redux/list.js
const INSERT = "list/INSERT";
export const insert = (name, age) => ({
type: INSERT,
memberlist: [{ name, age }],
});
const initialState = {
input: "",
memberlist: [
{ name: "temp", age: 15 },
{ name: "temp2", age: 12 },
],
};
const list = (state = initialState, action) => {
console.log(action.memberlist);
console.log(state.memberlist);
switch (action.type) {
case INSERT:
return { ...state, memberlist: [...state.memberlist, ...action.memberlist] };
default:
return state;
}
};
export default list;
Redux는 단일 스토어의 규칙을 고수하고있는데, 이때 store에 리듀서를 연결할 때도 단일 리듀서를 연결해야한다. 하지만
위와 같이 리듀서가 여러개인 경우 combineReducers를 사용하면 다양한 리듀서의 출력을 하나의 상태 트리로 결합할 수 있다.
// redux/rootreducer.js
import { combineReducers } from "redux";
import counter from "./counter";
import list from "./list";
const rootReducer = combineReducers({ counter, list });
export default rootReducer;
_app.jsx에서 state를 공유할 컴포넌트를 Provider 컴포넌트로 감싼다.
// pages/_app.jsx
import type { AppProps } from 'next/app'
import { Provider, } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from '../redux/rootreducer'
const store = createStore(rootReducer);
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</>
);
}
// components/CounterComponent.js
import { useDispatch, useSelector } from "react-redux";
import { useState } from "react"
function useGlobalItems() {
return useSelector((state) => state.counter); // - useselector 메서드를 이용해서 stoure에서 counter 데이터 가져오기
}
function CounterComponent() {
const dispatch = useDispatch();
const items = useGlobalItems();
const count = items.count;
const [numberValue, setNumber] = useState();
return (
<div>
<hr />
<div>
<h> Number Counter</h>
</div>
<div>
<div>{count}</div>
</div>
<div>
<button
onClick={() => dispatch({ type: 'INCREASE' })}>
증가
</button>
</div>
<input type="number" onChange={() => setNumber(event.target.value)} />
<button
onClick={() => dispatch({ type: 'SET_NUMBER', numberValue })}>
이동
</button>
<hr />
</div>
);
}
export default CounterComponent;
//component/ListComponent.js
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect } from "react";
import { insert } from "../redux/list"
import Member from "../compents/Member";
function useGlobalItems() {
return useSelector((state) => state.list); // - useselector 메서드를 이용해서 stoure에서 list 데이터 가져오기
}
function ListComponent() {
const dispatch = useDispatch();
const items = useGlobalItems();
const memberlist = items.memberlist;
const [name, setName] = useState("");
const [age, setAge] = useState();
return (
<div>
<div>
<h> Member List</h>
</div>
<input onChange={() => setName(event.target.value)} />
<input type="number" onChange={() => setAge(event.target.value)} />
<div>
<button
onClick={() => dispatch(insert(name, age))}>
추가하기
</button>
</div>
{
memberlist && memberlist.map((member) =>
<div>
<p>이름 : {member.name} / 나이 : {member.age}</p>
</div>
)
}
</div >
);
}
export default ListComponent;
redux의 액션 생성함수를 실행하여 리덕스 스토어에 변경된 상태값을 저장하기 위해서는 useDispatch라는 리액트 훅을 사용하여 액션을 실행시켜야 한다.
//pages/index.jsx
import CounterComponent from '../compents/CounterComponent'
import ListComponent from '../compents/ListComponent'
function Home() {
return (
<div >
<CounterComponent />
<ListComponent />
</div>
);
}
export default Home;