천진난만 코딩 스토리
2023.03.04) 항해 27일차 (1-Redux) 본문
오늘은 리덕스를 배웠다.
새로운 개념이라 긴장하고 좀 두려운 마음으로 리덕스를 접했다.
생각보다는 어떤 건지 감은 일찍 잡을 수 있었지만..............
머릿속에 콕 박히지 않았다.
그래서 중간에 머리를 식힐 겸 드라이브를 하고 와서
다시 보니 조금은 이해가 되긴 했지만
확실하게 이해하기 위해서는 내일 코드를 여러 번 쳐봐야 할 것 같다.
1) 리덕스란
리덕스란 “중앙 state 관리소”를 사용할 수 있게 도와주는 패키지(라이브러리)이다.
프론트엔드 개발자들은 “리덕스”를 전역 상태관리 라이브러리라고 많이 표현한다.
전역 상태, 즉 Global State를 의미하고 그것을 관리하게 도와주는 라이브러리 (패키지) 이기 때문이다.
① useState의 불편함
- 컴포넌트에서 컴포넌트로 State를 보내기 위해서는 반드시 부-모 관계가 되어야 한다.
- 조부모 컴포넌트에서 손자 컴포넌트로 값을 보내고자 할 때도 반드시 부모 컴포넌트를 거쳐야만 한다.
- 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없다.
이러한 불편함을 개선하는 그 새로운 도구가 바로 리덕스다.
우리가 리덕스를 사용하면 State를 공유하고자 할때 부-모 관계가 아니어도 되고, 중간에 의미 없이 컴포넌트를 거치지 않아도 된다.
자식 컴포넌트에서 만든 State를 부모 컴포넌트에서도 사용할 수 있게 된다.
② Global state와 Local state
- Local state (지역상태)
- 컴포넌트에서 useState를 이용해서 생성한 state 다. (좁은 범위 안에서 생성된 State)
- Global state (전역상태)
- Global state는 컴포넌트에서 생성되지 않는다.
- 중앙화된 특별한 곳에서 State들이 생성된다. (중앙 state 관리소)
앞으로 State를 Global state와 Local state라는 것을 따로 구분지어서 표현할 것이다.
2) 리덕스 설정
yarn add redux react-redux
패키지를 설치하고, 폴더를 생성한다.
- redux : 리덕스와 관련된 코드를 모두 모아 놓을 폴더
- config : 리덕스 설정과 관련된 파일들을 놓을 폴더
- configStore : “중앙 state 관리소"인 Store를 만드는 설정 코드들이 있는 파일
- modules : 만들 State들의 그룹
// src/configStore.js
import { createStore } from "redux"; //리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수)
import { combineReducers } from "redux";//여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어준다
const rootReducer = combineReducers({});
const store = createStore(rootReducer);
export default store;
//index.js
// 원래부터 있던 코드
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
// 우리가 추가할 코드
import store from "./redux/config/configStore";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
//App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어줍니다.
<Provider store={store}>
<App />
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
3) 카운터 프로그램 만들며 설정 이해하기
① 모듈 만들기
// src/modules/counter.js
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
위 코드에서 만든 State의 초기값은 { } (객체)이고, 그 안에 number라는 변수에 초기값 0을 할당해 준 것이다.
초기값은 꼭 객체가 아니고, 배열, 원시데이터, 여러 개의 변수를 가진 객체도 넣어줄 수 있다.
리듀서란, 변화를 일으키는 함수다.
리듀서의 인자에 보면 (state = intialState, action)이라고 되어 있는데,
리듀서 인자 첫 번째 자리에서는 state를, 두 번째 자리에서는 action이라는 것을 꺼내서 사용할 수 있다.
state = intialState처럼 state에 initialState를 할당해줘야 한다.
② 카운터 모듈을 스토어에 연결하기
모듈파일에서 초기 상태값과 리듀서를 작성했고, 이제 만든 모듈을 스토어에 연결시켜야 한다.
아직까진 모듈과 스토어가 각각 따로 분리되어 있는 상태이기 때문에 만든 State를 스토어에서 꺼낼 수 없다.
configStore.js 파일에 모듈을 연결시켜주어야 한다.
// src/redux/modules/config/configStore.js
// 원래 있던 코드
import { createStore } from "redux";
import { combineReducers } from "redux";
// 새롭게 추가한 부분
import counter from "../modules/counter";
const rootReducer = combineReducers({
counter: counter, // <-- 새롭게 추가한 부분
});
const store = createStore(rootReducer);
export default store;
③ 스토어와 모듈 연결 확인하기
"useSelector = 스토어 조회"
우리가 생성한 모듈을 스토어에 잘 연결했는지 확인하는 방법은 컴포넌트에서 스토어를 직접 조회하면 된다.
컴포넌트에서 리덕스 스토어를 조회하고자 할 때는 useSelector라는 ‘react-redux’의 훅을 사용해야 한다.
//useSelector의 사용법
// 1. store에서 꺼낸 값을 할당 할 변수를 선언합니다.
const number =
// 2. useSelector()를 변수에 할당해줍니다.
const number = useSelector()
// 3. useSelector의 인자에 화살표 함수를 넣어줍니다.
const number = useSelector( ()=>{} )
// 4. 화살표 함수의 인자에서 값을 꺼내 return 합니다.
// 우리가 useSelector를 처음 사용해보는 것이니, state가 어떤 것인지 콘솔로 확인해볼까요?
const number = useSelector((state) => {
console.log(state)
return state
});
이렇게 화살표 함수에서 꺼낸 state라는 인자는 현재 프로젝트에 존재하는 모든 리덕스 모듈의 state 인 것이다.
④ counter.js 모듈의 state 수정 기능 만들기 (+ 1 기능 구현해 보기)
리덕스에서 값의 수정은 리듀서에서 일어난다.
counter.js 모듈에 있는 number에 +1을 하고 싶으면,
- 리듀서에게 보낼 number를 +1 하라는 “Action”을 만든다.
- Action을 보낸다.
- 리듀서에서 Action을 받아 number +1을 한다.
Action을 코드로 나타내면 객체로 만들어서 이것을 액션 객체 라고 한다.
액션 객체는 반드시 type이라는 key를 가져야 한다.
액션 객체를 리듀서에게 보냈을 때 리듀서는 객체 안에서 type이라는 key를 보기 때문이다.
리덕스 모듈에 있는 state을 변경하기 위해서는 그에 해당하는 액션 객체를 모두 만들어줘야 한다.
❶ 액션 객체 보내기
액션객체를 보내기 리듀서로 보내기 위해서는 useDispatch라는 훅을 사용해야 한다.
react-redux에서 import 해서 사용할 수 있으며, 우리가 만든 액션 객체를 리듀서로 보내주는 역할을 하는 훅이다.
useDispatch라는 훅을 사용하기 위해서는 컴포넌트 안에서 아래와 같이 먼저 코드를 작성해서 dispatch라는 변수를 생성해줘야 한다.
이렇게 생성한 dispatch는 사용할 때 ()를 붙여서 함수를 실행하게 됩니다.
그리고 dispatch를 사용할 때 ( ) 안에 액션객체를 넣어주면 된다.
// src/App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux"; // import
const App = () => {
const dispatch = useDispatch(); // dispatch 생성
const number = useSelector((state) => state.counter.number);
return (
<div>
{number}
<button
onClick={() => {
dispatch({ type: "PLUS_ONE" });
//마우스를 클릭했을 때 dispatch가 실행되고, ()안에 있는 액션객체가 리듀서로 전달된다.
}}
>
+ 1
</button>
</div>
);
};
export default App;
❷ 액션객체 명령대로 리듀서가 state값을 변경하는 코드 구현하기
- 리듀서가 액션객체를 받아 상태를 바꾸는 원리
- 컴포넌트로부터 dispatch를 통해 액션객체를 전달받는다.
- action 안에 있는 type을 스위치문을 통해 하나씩 검사해서, 일치하는 case를 찾는다.
- type과 case가 일치하는 경우에, 해당 코드가 실행되고 새로운 state를 반환(return) 한다.
- 리듀서가 새로운 state를 반환하면, 그게 새로운 모듈의 state가 된다.
// src/modules/counter.js
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
console.log(action);
switch (action.type) {
// PLUS_ONE이라는 case를 추가한다.
// 여기서 말하는 case란, action.type을 의미한다.
// dispatch로부터 전달받은 action의 type이 "PLUS_ONE" 일 때
// 아래 return 절이 실행된다.
case "PLUS_ONE":
return {
// 기존 state에 있던 number에 +1을 더한다.
number: state.number + 1,
};
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
⑤ Action Creator
만약에 우리가 액션객체의 value를 변경할 일이 생긴다면 코드에서 모두 찾아서 변경해주어야 한다.
이렇게 하면 오타가 날 수도 있고 번거롭기 때문에 ,
액션객체를 한 곳에서 관리할 수 있도록 “함수"와 액션 value를 상수로 만들 수 있다.
// src/redux/modules/counter.js
const PLUS_ONE = "PLUS_ONE"; // value는 상수로 생성
// 액션객체를 반환하는 함수 생성
// export 가 붙는 이유는 plusOne()는 밖으로 나가서 사용될 예정이기 때문이다.
export const plusOne = () => {
return {
type: PLUS_ONE, // type에는 위에서 만든 상수로 사용 (vscode에서 자동완성 지원)
};
};
value를 상수로 생성하고,
dispatch() 안에 있던 액션객체를 import 한 Action Creator들로 변경한다.
// src/App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
// 사용할 Action creator를 import 한다.
import { plusOne } from "./redux/modules/counter";
const App = () => {
const dispatch = useDispatch();
const number = useSelector((state) => state.counter.number);
return (
<div>
{number}
<button
onClick={() => {
dispatch(plusOne()); // 액션객체를 Action creator로 변경한다.
}}
>
+ 1
</button>
</div>
);
};
export default App;
❶ dispatch() 안에는 반드시 객체만 들어가야 한다고 알았지만, 함수가 들어갈 수 있는 이유!
- {type: “PLUS_ONE”} === plusOne()는 같은 값이다.
- 함수를 실행한 것은 함수의 return 값과 같다.
- 즉, const one = () => {return 1; }로 함수를 만들었을 때 one() === 1이다.
❷ Action creator를 사용해야 하는 이유
- 휴먼에러 (오타) 방지
- 유지보수의 효율성 증가
- 코드 가독성 높임
⑥ payload
state를 변경할 때 리듀서에게 어떤 값을 같이 보내줘야 하는 경우,
payload를 액션객체에 같이 담아 보낼 수 있다.
// src/redux/modules/counter.js
// Action Value
const ADD_NUMBER = "ADD_NUMBER";
// Action Creator
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload: payload,
};
};
payload가 필요한 Action Creator에서는 함수를 선언할 때 매개변수 자리에 paylaod를 넣어줘야 한다.
Action Creator를 사용하는 컴포넌트에서 리듀서로 보내고자 하는 payload를 인자로 넣어줘야 하기 때문이다.
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
case ADD_NUMBER:
return {
// state.number (기존의 nubmer)에 action.paylaod(유저가 더하길 원하는 값)을 더한다.
number: state.number + action.payload,
};
default:
return state;
}
};
사용자가 컴포넌트에서 Action Creator로 payload를 담아 보내는 것은 액션객체에 담기고,
그렇게 담긴 것은 리듀서에서 action.payload에서 꺼내 사용해서 기존의 값에 더해줌으로써 기능을 구현하는 것이다.
'TIL(Today I Learned)' 카테고리의 다른 글
2023.03.06) 항해 29일차 (0) | 2023.03.07 |
---|---|
2023.03.04) 항해 27일차 (2-React Router Dom) (1) | 2023.03.04 |
2023.03.03) 항해 26일차 (3-React Hooks 최적화) (0) | 2023.03.03 |
2023.03.03) 항해 26일차 (2-React Hooks) (0) | 2023.03.03 |
2023.03.03) 항해 26일차 (1-CSS-in-JS,전역스타일링) (0) | 2023.03.03 |