위 Model, View, Controller를 통해 state를 관리하는 과정을 표현한것이다.
Redux가 사용되기전에는 데이터의 흐름관리는 MVC패턴을 이루었다.
M: (Model) 데이터 형식이나 구조를 관리
V: (View) 코드가 사용자에게 보여지는 시각적 부분 담당
C: (Controller) 변환되는 데이터를 관리
위 방법의 특징은 양방향 데이터 흐름이라는것으로 Data나 Code가 복잡해질수록 유지보수와 데이터의 신뢰성에 대해 문제가 생겼다.
Redux은 동적데이터(상태,state)를 편하게 사용 및 관리하기 위해 사용한다.
이때 상태란 두가지의 구분상태를 가진다.
상태 종류
1. 로컬: 특정 컴포넌트 안에서만 관리되는 상태
(React: 컴포넌트내에만 영향을 끼침)
2. 전역: 프로덕트 전체 혹은 여러 컴포넌트에서 관리되는 상태
(React: 다른컴포넌트와 상태를 공유)
위와 같이 JS에서는 전역변수를 많이 사용하는것을 추천하지 않는다. 하지만 경우에 따라 전역상태는 필요하며
한가지의 정보를 사용하는 두개의 컴포넌트를 예시로 들었을때 같은 정보를 다루지만 2개의 상태로 동기화과정이 따로 필요하다.
하지만 전역상태로 관리되면 하나의 상태를 공유하므로 별다른 동기화 과정이 필요가 없다.
★ Redux장점
1. 상태를 예측 가능하게 만들어 준다.
2. 유지보수(하나의 상태를 공유하니 유지보수에 용이하다.)
3. 디버깅에 유리하다.
4. 테스트를 붙이기
♦︎ Redux3가지 원칙
1. Single source of truth
Single source of truth(신뢰할수 있는 단일출처)원칙으로 같은곳에서 상태를 다룬다는 뜻이다.
Front-End뿐만아니라 다양한 나라에서 언급되는 원칙이다.
2. state is read-only
state를 변경하기 위해서는 action이라는 객체를 통해 변경을 해주어야 한다.state는 그러한 변경값을 읽기만 해야한다.
3. Changes are made with pure functions
변경은 순수함수로만 가능하다.
⚀ Store: 상태가 관리되는 오직 하나의 공간
⚀ Action: JS객체로 store에 어플리케이션의 데이터를 운반하는 역할
{ type: "ORDER" drink: { menu : "Americano", size : "Grande", iced : false } }
⚀ Reducer: 현재상태와 Action을 이용해 다음 상태를 만듦
1. UI에서 plus버튼이 클릭된다.
2. Dispatch에 전달인자로 Action객체를 포함한다.
3. 포함된 값이 Reducer로 전달된다.
4. Reducer는 Action객체의 type에 따라 동작을 수행한다.(action은 객체로 전달되어 type뿐 아니라 keyname을 지정하여 데이터도 전달 가능하다.)
5. 그동작의 결과로 새로은 New State가 return된다.
...
<body>
<button class="add">Add</button>
<span class='number'></span>
<button class="minus">Minus</button>
</body>
...
...
const ADD ="ADD"
const MINUS ="MINUS"
//countStore이라는 Store(저장소)를 생성하고 countModifier을 reducer로 연결하기
const countStore = createStore(countModifier)
//reducer로 action.type에 따라 count 값을 변경하여 return 한다.
const countModifier = (count = 0, action) => {
switch (action.type) {
case (ADD):
return count + 1
case (MINUS):
return count - 1
default:
return count
}
}
const onChange = () => {
number.innerText = countStore.getState()
}
//countStore(저장소가 변경될때마다 subscribe메소드를 활용해 onChange함수를 실행한다.
countStore.subscribe(onChange)
//버튼이 클릭될때마다 함수가 실행되어 dispatch로 reducer에 type을 전달한다.
const handleAdd = () => {
countStore.dispatch({ type: ADD });
};
const handleMinus = () => {
countStore.dispatch({ type: MINUS });
};
add.addEventListener("click", handleAdd);
minus.addEventListener("click", handleMinus);
//활용할 메소드를 import해줍니다.
import { useSelector, useDispatch } from 'react-redux';
import { createSelector } from 'reselect'
const store = createStore(rootReducer)
//많은 reducer을 사용할때 useSelector를 활용해서 컴포넌트와 state를 연결합니다.
//컴포넌트에서 useSelector 메소드를 통해 store의 state에 접근할 수 있는 것이죠.
//useSelector의 전달인자로는 콜백 함수를 받으며 콜백 함수의 전달인자로는 state 값이 들어갑니다.
const state = useSelector(state => state.emptyarr);//
//state는 빈 배열이다.
//useDispatch()는 Action 객체를 Reducer로 전달해주는 메소드입니다. Action 이 일어날만한 곳은 클릭 등의 이벤트가 일어나는 컴포넌트
const dispatch = useDispatch();
//handleClick이라는 함수가 실행되면 인자로 item을 넣어주고 dispath를 실행하고 그안에 addToCart라는 함수를 넣어준다.
handleClick = (item) => {
dispatch(addToCart(item.id))
}
//전달받은 item을 가지고 type과 객체를 생성하기
const addToCart = (item) => {
return {
type: ADD_TO_CART,
item:item
}
}
//빈 배열인 state에 입력받은 action객체에서 keyname으로 값을 호출하여 배열에 push하여 새로운 배열을 리턴한다.
const emptyarr = (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
return state.push(action.item) //[item] 출력
}
}
위 방법은 객체를 가시성있게 효과적으로 전달하기 위한 방법이며 좀더 직관적으로 적으면
handleClick = (item) => {
dispatch({
type: ADD_TO_CART,
item:item
})
}
위 방법으로도 가능하다. 하지만 많은양의 코드를 위와 같은 방법으로 작성하면 가시성이 떨어져보여 전자의 방법으로 진행하는것이 좋다.
TIP) React로 Redux로 리스트를 화면에 출력하는것을 구현할때 reducer로 리턴할배열이 얕은 복사로 하면 바뀌 배열이 출력이 바로 안된다.
이유: React는 새로운 address값의 배열이 생겨야지 리랜더링으로 다시 화면을 출력한다. 때문에 얕은복사(단순 변수에 "="을 붙이는 등의 방법은 바뀐 리스트를 바로 출력하지 않는다.