React redux 2

위범석·2022년 7월 19일
0

저번시간엔 그냥 state를 저장하고 사용하는 방법만 알아봤습니다.

그럼 이제 데이터 수정이 문제입니다.

redux를 쓴다면 state 데이터를 수정하고 싶을 때 그냥 대충 하지 않습니다.

  1. reducer 함수를 만들고 그곳에 데이터 수정하는 방법을 정의해놓습니다.

  2. 그리고 원하는 곳에서 dispatch() 라는 함수를 써서 reducer에게 수정해달라고 요청을 합니다.

꼭 이렇게 데이터를 수정하시길 바랍니다. 안그러시면 redux 쓰는 이점이 없음

왜 이런 헛짓거리를 시키는지도 마지막에 알아봅시다.

기본 개념부터 가보자..

참고 자료

Redux를 사용하면 ...
리덕스를 사용하면 하나의 store를 통해 모든 state와 상태 관리 로직을 저장, 유지할 수 있게 되며 원하는 Component로만 data를 전달할 수 있다.

https://velog.velcdn.com/images%2Fseungsang00%2Fpost%2F0fb87f26-2544-40ce-affb-c588872fddab%2Fimage.png

리덕스를 사용하면 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜서 더욱 효율적으로 관리 할 수 있고, 컴포넌트끼리 상태를 공유하게 될 때 여러 컴포넌트를 거치지 않고도 손쉽게 상태 값을 전달할 수 있다.
리덕스는 한 방향으로만 동작하기 때문에 데이터의 흐름을 예측하기 쉽다.
리덕스의 미들웨어라는 기능을 통하면 비동기 작업, 로깅 등의 확장적인 작업들을 더욱 쉽게 할 수도 있다.
리덕스는 시간여행형 디버거와 결합된 실시간 코드 수정과 같은 훌륭한 개발자 경험도 제공한다.

Redux의 3가지 원칙
리덕스에서 반드시 지켜져야 할 3가지 규칙이 있다.

  1. 하나의 애플리케이션 안에는 하나의 스토어(store)만 존재한다.
    리덕스에서는 하나의 App에는 하나의 스토어만 두어 여러 개의 스토어를 구독하여 발생할 수 있는 혼란을 피한다. dispatcher 동작 간에 하나의 스토어 상태를 구독하는데, 해당 스토어가 어딘지 찾기 어려워 진다.
    그로인해 생기는 혼란을 찾아내 수정하는 것도 어려워 진다.

때문에, 여러가지 reducer를 조합하여 하나의 store 로 생성한다. combineReducers 메서드를 사용해 여러개의 reducer를 하나의 store 로 구성할 수 있다.

특정 업데이트가 너무 빈번하게 일어나거나, 애플리케이션의 특정 부분을 완전히 분리시키게 될 때 여러개의 스토어를 만들 수도 있지만 권장 사항이 아니며, 여러개의 스토어가 존재하는 경우에는 리덕스 개발 도구를 사용하지 못한다.

  1. 상태(state)는 읽기전용(read-only)이다.
    리덕스에서도 리액트와 마찬가지로 기존의 상태는 건들이지 않고 새로운 상태 객체를 생성하여 상태를 업데이트 해준다. 이를 위해 spread-syntax(...)나 concat, Object.assign 등을 사용한다.

이렇게 상태의 불변성을 유지시켜주면 나중에 개발자 도구를 통해서 뒤로 돌릴 수도 있고 다시 앞으로 돌릴 수도 있다.
리덕스에서 불변성을 유지해야 하는 이유는 내부적으로 데이터가 변경 되는 것을 감지하기 위하여 shallow equality 검사를 하기 때문이다. 이를 통하여 객체의 변화를 감지할 때 객체의 깊숙한 안쪽까지 비교를 하는 것이 아니라 겉핥기 식으로 비교를 하여 좋은 성능을 유지할 수 있다.
3. 리듀서는 "순수함수"여야 한다.
순수함수는 동일한 인풋이라면 언제나 동일한 아웃풋이 있어야 한다. 즉, 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과값을 반환해야한다.

리듀서 함수는 파라미터로 state와 action 객체를 받는다. 이때, 리듀서 함수는 인자로 받아온 state는 변경하지 않고, action을 통해 변경한 새로운 state 객체를 만들어서 반환해야한다.

순수하지 않은 작업들
new Date(), Math.random(), axios.get() 와 같은 일부 작업들은 실행 할 때마다 다른 결과값이 나타날 수 있다. 이런 작업들은 순수하지 않은 작업이므로, 리듀서 함수의 바깥에서 처리해주어야 한다. 이러한 작업들을 처리해주기 위해 리덕스 미들웨어를 사용한다.

Action(액션)
state 가 업데이트될 때, 어떻게 업데이트 할지를 정의해주는 객체.
쉽게 말해 액션이란 어플리케이션에서 일어나는 모든 사건들 중에서 상태 변화가 필요한 사건들을 말한다.
Redux에서는 어떤 사건이 발생하여 state 값 변경이 필요하면 action 을 발생시킨 후, 이 객체를 dispatch() 함수의 인자로 넘겨준다. 그러면 dispatch() 함수가 Reducer 함수를 호출해 Reducer 함수를 실행시켜 새로운 state를 생성한다.

action 객체는 type을 필수로 가지고 있어야한다(그 외의 값은 자유롭게 추가해주면 된다). action의 type은 일반적으로 문자열 상수로 정의된다.
const ADD_TODO = 'ADD_TODO' // action의 type을 정의

정의된 action type은 action creators(액션 생성자 함수)를 통해 사용된다.
const ADD_TODO = 'ADD_TODO'

const addTodo = (text) => {
return {
type: ADD_TODO,
text
}
}

액션 생성자 함수(Action creator)
액션 생성함수를 만들어서 사용하는 이유는 나중에 컴포넌트에서 더욱 쉽게 액션을 발생시키기 위함이다. 그래서 보통 함수 앞에 export 키워드를 붙여서 다른 파일에서 불러와서 사용한다.

export const addTodo = (text) => {
return {
type: ADD_TODO,
text
}
}
리덕스 사용 시 액션 생성함수를 사용이 필수는 아니다. 액션을 발생시킬 때마다 직접 액션 객체를 작성해도 된다.

reducer

reducer는 두 가지 인자를 받는데, 첫 번째는 이전 상태 정보(state)가 들어오고 두 번째 인자는 아까 위에서 발생한 액션 객체(action)가 들어온다. 리듀서 함수가 상태를 업데이트하면 그에 따라서 render가 다시 일어나 화면이 바뀌게 된다.
리듀서는 받아온 현재 state에 action을 적용한 새로운 state 를 리턴해주는 함수다.

리듀서는 state와 action 객체를 파라미터로 받아온다.
state가 객체나 배열일 경우 스프레드 연산자를 사용하거나 concat 메소드를 사용하는 식으로 원래의 배열이나 객체(state)를 수정하지 않아야 한다는 점에 주의하자!
리듀서 함수에서는 action의 type에 따라 변화된 state를 반환하게 된다.

const itemReducer = (state = initialState, action) {
switch (action.type) {
case ADD_TO_CART:
return Object.assign({}, state, {cartItems: [...state.cartItems, action.payload]});
case REMOVE_FROM_CART:
const filtered = state.cartItems.filter(el => el.itemId !== action.payload.itemId);
return { ...state, cartItems: filtered };
default:
return state;
}
};
useReducer 에서와 달리, 리덕스의 리듀서에서는 default: 부분에 기존 state를 그대로 반환하도록 작성해야 한다.

  • useReducer 에선 일반적으로 default: 부분에 throw new Error('Unhandled Action') 과 같이 에러를 발생시키도록 처리하는게 일반적이다.

store

스토어는 어플리케이션의 상태가 보관되는 하나의 저장소이다. 어플리케이션의 모든 상태는 하나의 스토어에서 관리된다.
스토어 안에는 현재의 앱 상태state와 리듀서reducer, 추가적인 내장 함수들(dispatch, subscribe 등)이 들어있다.

import { createStore } from 'redux';
import rootReducer from '../reducers/index';

const store = createStore(rootReducer);
이처럼 store을 생성하고 reducer을 연결하여 어플리케이션에 연결하게 된다.

useSelector
const items = useSelector((store) => store.cartReducer);
useSelector를 통해 스토어의 특정 state를 가져올 수 있다.

리덕스로 구현한 간단한 쇼핑몰 어플리케이션에서 Add to Cart 버튼 클릭 시 Shopping Cart의 뱃지의 숫자가 늘어나게 되는 과정의 구동 방식을 정리해보면 아래와 같다.

profile
코린이

0개의 댓글