[React] 리덕스
어렵다…리덕스는 어렵다…난해하다…쓰고, 또 쓰고, 정리해야겠따..첫 고비다
리덕스의 개념
리덕스는 상태관리를 효율적으로 하기위한 라이브러리이다.
리덕스는 리액트뿐만아니라 바닐라 JS, Angular, Vue(Vuex를 더 많이쓰긴 함) 등등 여러 곳에서 유연하게 쓰일 수 있다.
리액트는 Context API를 쓰면, 전역상태
를 관리하기 위한 별도의 라이브러리가 따로 필요하진 않다. 작은 프로젝트에서는 오히려 Context API가 효율적일 수 있다.
심지어 hook
이 도입되고, useContext Hook
을 사용하면 context API를 정~말 쉽게 사용할 수 있다.
하지만, 서버에서 데이터를 끌어와야하는 비동기적인 작업들을 처리하기 위해선 redux-middleware
를 사용해야하고, 기업에서 운영중인 큰 프로젝트에서는 redux
가 많이 쓰이기에 알아두어야한다.
why Redux?
-> (코딩호주니님 유튜브)[https://www.youtube.com/watch?v=xsOhUX7DDl0&t=1048s]
상태관리 라이브러리로 MobX
라는 것도 있다.
MobX는 데이터를 빠르게 처리하고, 비동기를 쉽게처리할 수 있는 등 여러 장점이 있었음에도 에어비엔비는 redux로 변경했다.
MobX의 데이터가 반응형으로 이루어져 프론트엔드에서 반응형으로 변환을 시켜야하는데, 복잡한 데이터 구조에서는 이러한 작업이 시간이 많이 걸리게된다.
즉, 처리속도는 빠른데, 변환이 느림
이다.
대용량 데이터를 사용해야하는 큰 기업에선 쓰기에 적합하지 않아 리덕스로 바꿨다.
용어
useReducer
라는 훅을 공부했다면 크게 어렵지 않다. 이 훅에서 사용되는 용어가 중복되는 부분이 있다.
주된 흐름은 이와같다.
Action
상태 변화를 언어적으로 정의
하는 객체.
{type: 'CHANGE_INPUT',
input: input }
useReducer
에서는 type
필드가 필요없었지만, redux
에서는 반드시 액션객체가 type
필드를 가져야한다.
나머지 값들은 개발자가 상태 업데이트시 참고할 값이고, 작성자 마음대로해도 상관없다.
어차피 initialState
를 기반으로 값이 바뀌기 때문이다.
Action Creator
액션 객체를 만들어준다.
export const decrease = () => ({ type: DECREASE });
위의 action
객체를 함수로 정의한 것이다.
일일히 action을 정의하는 것이 귀찮기 때문에, 컴포넌트 상에서 일어나는 이벤트에 대해 생각해보고, 필요한 파라미터를 넣어서 객체를 정의하고, 리턴한다.
Reducer
reducer
는 state, action값을 받아 새로운 state를 생성하는 함수이다.
짧게 말하자면 변화를 일으키는 함수
이다.
이미 설정된 action
값을 파라미터로 받아와서 action의 type에 따라 state를 변화시킨다.
function counter(state = initialState, action) {
switch (action.type) {
case INCREASE:
return {
number: state.number + 1,
};
case DECREASE:
return { number: state.number - 1 };
default:
return state; //초기렌더링시 리턴
}
}
Store
프로젝트에 리덕스를 적용하기 위한 변수
import { createStore } from "redux";
import { Provider } from "react-redux";
//redux-store
const store = createStore(rootReducer);
ReactDOM.render(
//store선언시 index.js에서 감싸야한다.
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
createStore
로 reducer함수를 적용한 store변수를 만든다.
이를 Provider 컴포넌트에 props로 넣어준다.
이제 App컴포넌트는 Consumer
로써 store에서 state를 구매할 수 있다!~!
Dispatch
dispatch는 정의해놓은 reducer의 action을 실행한다.
이에따라 reducer가 새로운 state를 만들어준다.
subscribe
상태가 업데이트될때마다 자동적으로 호출하게해주는 내장함수.
const listener = () => {
console.log("상태업뎃!");
};
const unsubscribe = store.subscribe(listener);
unsubscribe();
리스너 함수를 인자로 넣어주면 store의 상태가 업데이트될때마다 호출된다.
바닐라JS redux예시
구현하고자 하는 것은 2가지 컴포넌트의 state를 관리하는 store를 만드는 것.
1번 컴포넌트는 클릭 될때마다 state가 반전되며 색이 나타난다.(TOGGLE)
2번 컴포넌트는 2개의 버튼, +1과 -1을 구현.
기본설정
<html>
<head>
<link rel="stylesheet" href="index.css" type="text/css" />
</head>
<body>
<div class="toggle"></div>
<hr />
<h1></h1>
<button id="increase">+1</button>
<button id="decrease">-1</button>
<script src="./index.js"></script>
</body>
</html>
CSS
.toggle {
border: 2px solid black;
width: 64px;
height: 64px;
border-radius: 32px;
box-sizing: border-box;
}
.toggle.active {
background: yellow;
}
.active라는 클래스가 생기면 버튼의 색이 변한다.
JS
DOM 레퍼런스
//DOM 레퍼런스
const divToggle = document.querySelector(".toggle");
const counter = document.querySelector("h1");
const btnIncrease = document.querySelector("#increase");
const btnDecrease = document.querySelector("#decrease");
쿼리셀렉터로 버튼의 DOM객체를 가져온다.
Action, Action Creator
// 액션타입
const TOGGLE_SWITCH = "TOGGLE_SWITCH";
const INCREASE = `INCREASE`;
const DECREASE = `DECREASE`;
//액션생성함수
const toggleSwitch = () => ({ type: TOGGLE_SWITCH });
const increase = (difference) => ({ type: INCREASE, difference });
const decrease = () => ({ type: DECREASE });
Action Creator는 액션 객체를 만들어준다.
difference는 type외의 다른 파라미터를 받을 수 있게 해준다.
initialState
//초기값
const initialState = {
toggle: false,
counter: 0,
};
관리해야하는 state는 initialState에 객체형태로 보관.
reducer
function reducer(state = initialState, action) {
switch (action.type) {
case TOGGLE_SWITCH:
return {
...state, //spread문법, 불변성유지
toggle: !state.toggle,
};
case INCREASE:
return {
...state,
counter: state.counter + action.difference,
};
case DECREASE:
return {
...state,
counter: state.counter - 1,
};
default:
return state;
}
}
reducer함수는 인자로 initialState와 action을 받는다.
INCREASE의 action.difference
는 action creator에서 선언해놓은 키값이다.
action을 파라미터로 받기때문에, INCREASE일때만 접근가능.
그리고 action 타입에 따라 state를 업데이트하고, 리턴해주는 역할을 한다.
store
import { createStore } from "redux";
const store = createStore(reducer);
reducer함수를 인자로 받는 createStore를 import하고, store를 선언해준다.
store내장함수 사용
//렌더함수 정의
const render = () => {
const state = store.getState(); //현재 상태 로드
//토글
if (state.toggle) {
divToggle.classList.add("active");
} else {
divToggle.classList.remove("active");
}
counter.innerText = state.counter;
};
render();
렌더링하는 함수를 정의한다.
store.getState
는 store에 있는 현재 상태를 로딩한다.
subscribe
store.subscribe(render);
위의 정의한 render
함수를 store가 구독하면, store가 바뀔때마다 render함수가 호출된다.
dispatch
divToggle.onclick = () => {
store.dispatch(toggleSwitch());
};
btnIncrease.onclick = () => {
store.dispatch(increase(1));
};
btnDecrease.onclick = () => {
store.dispatch(decrease());
};
각 버튼에 이벤트리스너를 달아준다.
실행순서는
- 각 버튼에 이벤트가 발생
- store의 내장함수 dispatch로 인자로받는 액션생성함수에따라 dispatch를 실행
- store의 state 업데이트
- subscribe에 의해 render함수 실행
리덕스 규칙
단일 스토어
하나의 앱에 하나의 스토어를 사용한다.
앱의 특정 기능을 완전히 분리하고 싶을때는 여러개의 스토어를 만들 수 있지만 상태관리가 복잡해질 수 있음.
읽기 전용
불변성을 지키라는 말이다.
기존의 객체를 읽어서 복사한다음, 복사한 객체에 업데이트를 수행하고 복사한 객체로 state를 업데이트해라.
컴포넌트 성능을 최적화할때, 공부한 내용과 똑같다.
reducer는 순수함수여야한다.
- reducer는 prevstate와 action object를 파라미터로 받는다.
- 파라미터 값 외에 의존해선 안된다.
- 동일한 파라미터에 대해 항상 동일한 결과가 보장되어야한다.(네트워크 요청, 랜덤한 수, 시간가져오기 등은 허용X)
- 불변성을 유지해야한다.
랜덤한 수나 시간을 가져오는 등의 작업은 항상 새로운 메모리주소를 가진다.
리덕스는 얕은 복사로 객체의 메모리주소를 참조해 같은지, 다른지만 비교하므로 다른 state는 변경된 것이 없어도, 랜덤작업으로 인해 불필요한 렌더링이 발생한다는 것이다.
이런 작업은 리듀서 함수에서 실행되어선 안된다.
댓글남기기