React - Redux 기초 개념 정리
Redux
리덕스란 ? "자바스크립트 앱을 위한 예측 가능한 상태 컨테이너" 라고 공식문서에 정의하고 있다. 요즘은 Redux Toolkit
이 떠오르고 있다고 들었는데, 인기 많은 전역 상태 관리 도구이다.
리덕스는 리액트 도구가 아니다 !! 리덕스는 바닐라 JS, React, Vue..... 등 다양한 곳에서 사용이 가능하다. 다만 컴포넌트에 집중된 리액트와 시너지가 많아 리액트에서 많이 사용할 뿐이라 들었다.
여기서는 React와 Redux를 다둘 예정이다.
리액트와 리덕스는 왜 시너지가 좋은가?
리액트는 컴포넌트에 집중되어있다. 그리고 데이터를 props,state(부모 데이터, 자신 데이터)를 사용한다.
여러 문제가 있지만, 흔히 말하는 Prop Driling의 비 효율적인 부분과 규모가 커질 수록 props 경로를 찾기 어렵다는 단점, 형제 컴포넌트 간에 데이터 전달이 어렵다는 점이 있다.
그리고 Redux가 이를 해결해준다.
Redux는 전역 상태 관리로써 쉽게 표현하자면 중앙 저장소에 모든 데이터를 모아 놓고, 필요로 하는(구독하는) 컴포넌트에 데이터를 전달해준다.
단, 모든 데이터가 모여있기 때문에 직접 변경의 위험도가 높다. 따라서 데이터를 직접 수정 할 수 없고, 값을 변경하거나 가지고 올 때에는 특정 함수를 통해 교환한다는 규칙이 있다.
React에서 Redux
Redux 흐름
데이터를 원하는 컴포넌트는 스토어(데이터 저장소)에 데이터를 받는다.
데이터 수정을 원하는 컴포넌트는 액션을 통해 리듀서에게 바꿀 명령과 데이터를 전달하고 리듀서는 이를 처리하여 스토어에 있는 데이터를 변경한다.
Redux에 규칙
store( 데이터 저장소)는 한 프로젝트에 하나만 사용한다.
store에 데이터를 수정하기 위해서는 오직 action 함수로만 변경이 가능하다.
이 말은 리액트 useState와 유사하게 필요한 데이터를 마구잡이로 변하지 않도록 불변성을 유지 시켜주기 위함이다. 즉 허락 없이 변경 할 수 있고 더 좁은 의미로 말하자면 store에 있는 데이터 즉 state는 읽기 전용 데이터입니다.어떤 요청이라도 reducer는 같은 동작을 해야한다.
reducer는 순수 함수여야 한다. 즉 파라미터 외의 값에는 의존하지 않아야 하고, 이전 상태는 건들지 않고 새로운 데이터를 반환 해야한다.
또한 같은 파라미터를 받는다면 항상 같은 값을 반환 해야한다.Redux 구조 용어
state
구조 용어라 말하긴 뭐하지만 우리가 생각하는 state가 맞다. 리덕스에 저장하고 있는 상태, 즉 데이터를 말한다.action Creator (액션 생성 함수)
const changeState = (new_data) => { return { type: 'CHANGE_STATE', data: new_data } } // return 하는 객체가 액션이다
데이터를 Reducer에 전달하기 위함이고 데이터를 변경하기 위해서 꼭 거쳐야 하는 관문이다.
여기서도 지켜야 할 규칙이 있는데. **첫 번째**는 return은 딕셔너리 형태로 반환을 해야한다. **두 번째**는 type이 꼭 들어가야 한다. ( key값을 type >> main으로 불가) ###### tip. type의 들어가는 문자열을 변수로 선언하면 오타를 발생 할 확률이 적다. reducer에서도 똑같이 작성해야 하기 때문에 유지 관리가 편하다. ``` const changeState = "CHANGE_STATE"```Reducer
개인적으로 제일 어려운 개념이었는데, 실질적으로 store에 있는 데이터를 변경하는 함수다.
액션 생성 함수가 호출되면 가지고 있는 데이터를 Reducer에게 전달하게 된다. 그리고 리듀서는 그 데이터를 가지고 새로운 데이터를 만들고 리턴을 해준다.// 기본 상태값을 임의로 정해줬어요. function reducer(state = [], action) { switch(action.type){ case CHANGE_STATE: return [...state, action.data]; default: return false; } }
코드를 살펴 보면, 우선 reducer에 전달되는 파라미터 중** state는 현재 state를 받는다. 여기선 default 값으로 빈 배열을 준다. 그리고 *action** 은 액션 생성 함수에서 전달한 객체를 받는다.
{ type: 'CHANGE_STATE', data: new_data }
꼭 switch ~ case를 사용해야 하는 건 아니지만, 이 방법이 편해 보인다.여기서 중요하게 볼 부분은 return이다. state는 직접 수정이 불가능하다 어떻게 보면 읽기 전용 데이터라고 앞서 말했었다. 즉 배열의 일부, 혹은 객체의 일부 등 데이터 일부분만 수정하는 건 불가능하다.
return [...state, action.data];
이 코드를 보면 알 수 있지만, 현재 가지고 있는 state와 새로 받은 데이터를 합쳐 새로운 배열을 return한다. 즉 수정보다는 새로운 데이터를 만들고 이 데이터를 store에 저장하는 방식이다.
- dispatch
store의 내장 함수로 주 역할은 액션 함수를 호출하는 역할을 한다.
store.dispatch(changeState({.....}));
너무 간단하게 표현했지만, 실제는 이게 맞는 것 같다. dispatch의 인자로 액션 생성 함수를 받아 해당 함수를 실행시킨다.
그리고 액션 함수는 받은 데이터를 reducer에 전달한다.
구조적인 부분은 생략하겠다. 공식 문서에 너무 잘 나와있다. 그리고 구글에도 널렸다 .....
yarn add redux
npm install redux
// react-redux를 설치하면 더 편한 Hook을 사용 할 수 있다.
useSeletor, useDispatch 훅
특이하게 나는 훅으로 작성하는 방법을 먼저 배웠다. 단점은 아마 react 말고는 훅이 없으니 작성할 줄 모른다는 점? 하지만 흐름을 알 수 있기 때문에 조금의 학습으로 다룰 수 있을 것이라 생각한다.
우선 훅이 없는 코드를 보면 mapStateToProps
를 사용하여 데이터를 받을 수 있다.
function getcurrentState(state, ownProps){
return {check : true}
}
export default connect(getCurrentState)(Home);
이 코드에 의미는 Home이라는 컴포넌트에 데이터를 연결하겠다는 의미이다.getCurrentState
는 2가지 aurguments와 호출하는데 첫 번째는 state, 두번째는 컴포넌트의 props다.
이 방식을 사용하면 Home 컴포넌트에 props로 redux 데이터를 전달 받게되고 그대로 사용하면 된다.
즉 위에서 props.check >>> true
당연히 return값을 다르게 하면 그 데이터를 받을 수 있다. (state를 이용해서 원하는 데이터를 가져오자)
또한 액션 생성 함수를 사용하려면 store.dispatch(<액션 생성 함수()>)
방식으로 호출 한다.
다음은 훅이 있는 코드이다. 개인적으로 위 과정이 너무 간결해 진다.
import { useDispatch, useSelector } from "react-redux";
import { deleteMemoFB} from "./redux/modules/memo";
const dispatch = useDispatch(); // dispatch 사용 준비
const textMemo = useSelector((state) => state); // textMemo 변수에 state 할당
dispatch(deleteMemoFB(index_id)); // 액션 실행 함수(deleteMemoFB) 호출 및 데이터 전달
개인적으로 이 방법이 너무 간단하고 편해 보인다. connect 할 필요도 없고, 필요한 값만 import하여 사용하는 방법이다.