포깟몬 프로젝트를 어쩌다보니 useContext까지 끝내버린 림졍.
학구열이 갑작스럽게 높아진(?) 그녀는 자기도 모르게 TIL를 써버리게 되었는데...
(실은 오블완 챌린지 다 해치우고.. 싶달까..? 후후..)
빠르게 가시죠. 일단 고.
React (리액트) 강의 정리 - 숙련 ver. (이이이어서)
리덕스... 리덕수... 이덕수씨...!!!!
redux 흐름
- View 에서 액션 발생
- dispatch action 발생
- reducer 실행(by action) 전, middleware 작동
- middleware 에서 명령 수행 후, reducer 함수 실행 (3, 4번은 아직 몰라도 된다!)
- reducer 실행결과, store에 새로운 값으로 저장
- store의 state, subscribe 하고 있던 UI에 변경된 값 지정
<counter.js 모듈의 state 수정 기능 만들기 (+ 1 기능 구현해보기)>
어떻게 counter.js 모듈에 있는 state의 값을 변경할 수 있을까?
리덕스에서는 값의 수정은 리듀서에서 일어남!
리듀서 : 디스패치를 통해 전달받은 액션객체를 검사 + 조건이 일치 시 새로운 상태값을 만들어내어 “변화를 만들어내는" 함수
만약에 counter.js 모듈에 있는 number에 +1을 하고 싶다면?
- 리듀서에게 보낼 number를 +1 하라는 “명령”을 만든다.
- 명령을 보낸다.
- 리듀서에서 명령을 받아 number +1을 한다.
1) 리듀서에게 보낼 “명령” 만들기
리듀서에게 number에 +1을 하라고 명령(Action)을 보내야함.
- 즉, 리듀서에게 원하는 Action을 미리 정해주는 것. (액션 객체로 표현가능)
액션 객체, 반드시 type이라는 key를 가져야 함! (리듀서 전송 시, 리듀서는 객체 안에서 type이라는 key를 보기 때문.)
( = 리듀서로 보낼 “명령" )
// 예시 코드
//number에 +1 을 하는 액션 객체
{ type : "PLUS_ONE" };
∴ 리덕스 모듈에 있는 state을 변경하기 위해서는 그에 해당하는 액션 객체를 모두 만들어줘야 함.
(중요) 액션객체 type의 value는 대문자로 작성한다. (JS에서 상수는 대문자로 작성하는 룰이 있음)
2) “명령”(액션 객체) 보내기
이제 명령을 만들었으니 이제 보낼 차례겠죠?
dispatch 사용법 : ' dispatch = 함수 '로 생각, 사용할 때 () 를 붙여서 함수를 실행.
간단하게 설명하자면.. 스토어의 내장함수 중 하나, 액션객체 → 리듀서로 보내는 “전달자” 함수?
디스패치(dispatch)를 사용하기위해서는 useDispatch() 라는 훅을 이용
∴ 우리는 ' useDispatch '라는 훅을 활용하여 리듀서에게 해당 액션 객체를 보내줄 예정
useDispatch : 액션 객체를 리듀서로 보내주는 역할을 하는 훅 (react-redux에서 import 해서 사용 가능)
+) 액션을 파라미터로 전달 → () 안에 액션개체를 넣어주면 ok .. dispatch(action) 처럼
// src/App.js
import React from "react";
import { useDispatch } from "react-redux"; // import 해주세요.
const App = () => {
const dispatch = useDispatch(); // dispatch 생성
return (
<div>
<button>+ 1</button> {/* 버튼을 하나 추가해주세요. */}
</div>
);
};
export default App;
// src/App.js
import React from "react";
import { useDispatch } from "react-redux"; // import 해주세요.
const App = () => {
const dispatch = useDispatch(); // dispatch 생성
return (
<div>
// (1) 이벤트 핸들러 추가
// (2) 마우스를 클릭 시, dispatch 실행, ()안에 있는 액션객체 리듀서로 전달.
<button
(1)
onClick={() => {
(2)
dispatch({ type: "PLUS_ONE" });
}}
>
+ 1
</button>
</div>
);
};
export default App;
3) dispatch를 이용해서 보낸 액션객체를 받기
// src/redux/modules/counter.js
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
리듀서에 action을 콘솔로 찍어보며는..
// src/redux/modules/counter.js
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
console.log(action); // 여기에 console.log(action) 추가
switch (action.type) {
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
리듀서에있는 action = App.js → dispatch로 보낸 액션객체
// 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;
리듀서, state값 변경하는 코드 구현 과정
- 컴포넌트로부터 dispatch 통해 액션객체를 전달
- action 내부 type, 스위치문을 통해 일일이 검사, 일치하는 case 탐색
- type과 case가 일치하는 경우, 해당 코드가 실행 + 새로운 state 반환(return)
- 리듀서, 새로운 state를 반환 시, 그게 새로운 모듈의 state가 됨.
[정리]
- action이 {type: “PLUS_ONE”} 이기 때문에, 리듀서 안에 있는 스위치문은 action.type을 조회히고
이것이 일치하면 로직을 기반으로 새로운 state반환.
추가) useSelector를 활용한 변경된 state값 확인
- 버튼을 누를때마다 number가 1씩 증가 (화면/ 콘솔 둘 다 출력)
// src/App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
const App = () => {
const dispatch = useDispatch();
// 👇 코드 추가
const number = useSelector((state) => state.counter.number);
console.log(number); // 콘솔 추가
return (
<div>
{/* 👇 코드 추가 */}
{number}
<button
onClick={() => {
dispatch({ type: "PLUS_ONE" });
}}
>
+ 1
</button>
</div>
);
};
export default App;
Action Creator
특정 액션 객체를 쉽게 만들기 위해 사용하는 함수
만약에 액션객체의 value를 변경할 일이 생겼다.. 근데 수정해야 할 작업이 3-4개가 아니라 몇백개 이상이라면? 😨
하드 코딩으로 발생할 수 있는 이러한 human error 때문에
액션 객체를 한 곳에서 관리할 수 있도록 "함수"와 액션 value를 상수로 만들기 시작
→ Action Creator 의 탄생
// src/modules/counter.js
// 추가된 코드 👇 - 액션 value를 상수들로 만들어 줍니다. 보통 이렇게 한곳에 모여있습니다.
const PLUS_ONE = "PLUS_ONE";
const MINUS_ONE = "MINUS_ONE";
// 추가된 코드 👇 - Action Creator를 만들어 줍니다.
export const plusOne = () => {
return {
type: PLUS_ONE,
};
};
export const minusOne = () => {
return {
type: MINUS_ONE,
};
};
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
case PLUS_ONE: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣어줍니다.
return {
number: state.number + 1,
};
case MINUS_ONE: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣어줍니다.
return {
number: state.number - 1,
};
default:
return state;
}
};
export default counter;
- action creator 사용해보기 (아래 예시 참고)
1. export 된 action creator를 import
2. dispatch()에 있던 액션객체 제거, action creator를 추가하여 생성됭 객체 활용하면 됨
// src/App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
// 사용할 Action creator를 import 합니다.
import { minusOne, 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>
{/* 빼기 버튼 추가 */}
<button
onClick={() => {
dispatch(minusOne()); // 액션객체를 Action creator로 변경합니다.
}}
>
- 1
</button>
</div>
);
};
export default App;
Action Creator를 사용하는 이유
(1) 휴먼에러 (오타) 방지
액션객체의 type value = 상수. 자동완성등의 보조 기능 지원 (in 개발 tool)
∴ 의도치 않은 휴먼에러(오타) 방지 가능
(2) 유지보수의 효율성 증가
단 한번의 수정으로 전체 모든 수정사항을 반영이 가능함.
(3) 코드 가독성
정리가 자알 되어있으면, 다른 개발자가 보았을 때도 모든 Action들을 한눈에 알 수 있음
즉, 그 자체가 Action 들의 리스트업을 해주는 역할을 가짐
(4) 리덕스 공식문서에서 소개되고 있는 방법
리덕스팀에서도 위와 같은 사유에 근거하여 공식적으로 안내하고 있는 방법일 것 입니다.
Payload
리듀서가 상태 변경에 사용하는 실제 데이터 (액션 객체가 전달하는 데이터....)
지금까지는.. 리듀서에게 액션 객체를 보낼 때, 목적어 없이 그냥 보냄..
이제는 ~ 을 이라는 목적어도 포함된 명령어를 보내주기 위해 탄생되었다고 생각하면 됨..ㅇㅇ
+) type과의 차이
type = 액션의 성격(데이터의 유형)을 정의 / payload = 그 액션이 수행할 구체적인 데이터를 제공
// payload가 추가된 액션객체 : 리듀서에게 어떤 값을 같이 보내줘야 한다? payload + 액션객체 같이 담아주기
{type: "ADD_NUMBER", payload: 10} // type뿐만 아니라 payload라는 key와 value를 같이 담는다.
추가) 꼭 payload라는 이름을 통해서 보내야하나용?
→ ...아뇨사실 굳이 payload라는 이름을 딱 정해서 쓸 필요는 없음
{type: "ADD_NUMBER", num: 10} // ??
{type: "ADD_NUMBER", number: 10} // ??
{type: "ADD_NUMBER", data: 10} // ??
{type: "ADD_NUMBER", myNumber: 10} // ??
{type: "ADD_NUMBER", myNum: 10} // ??
{type: "ADD_NUMBER", payload: 10}
==> payload가 아닌 다른 프로퍼티 이름을 사용해도 무관(개발자 마음이다!), but "커뮤니티 best practice" 로 공유되면서
많은 개발자들이 데이터는 payload라는 프로퍼티에 담아주고 있기 때문에 일반적으로 payload라고 씀. (커뮤니티 컨벤션)
- payload를 이용하여 기능 구현하기
[기능 구현 작업 순서]
- 입력한 값을 받을 input 구현
- Action Creator 작성
- 리듀서 작성
- 구현된 기능 테스트
1) (사용자가) 입력한 값을 받을 Input 구현
// src/App.js
import React from "react";
import { useState } from "react";
const App = () => {
const [number, setNumber] = useState(0);
const onChangeHandler = (event) => {
const { value } = event.target;
// event.target.value는 문자열 입니다.
// 이것을 숫자형으로 형변환해주기 위해서 +를 붙여 주었습니다.
setNumber(+value);
};
// 콘솔로 onChangeHandler가 잘 연결되었는지 확인해봅니다.
// input에 값을 넣을 때마다 콘솔에 그 값이 찍히면 연결 성공!
console.log(number);
return (
<div>
<input type="number" onChange={onChangeHandler} />
<button>더하기</button>
<button>빼기</button>
</div>
);
};
export default App;
2) Action Creator 작성 - counter.js 모듈 작성
* 만들어야 할 것을 리스트업 (이건 습관 들여놓으면 좋을듯?)
// src/redux/modules/counter.js
// Action Value
// Action Creator
// Initial State
// Reducer
// export default reducer
요로코롬 ~.~...
// src/redux/modules/counter.js
// Action Value
const ADD_NUMBER = "ADD_NUMBER";
// Action Creator
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload: payload,
};
};
// Initial State
// Reducer
// export default reducer
추가) payload가 필요한 Action Creator의 경우, 함수 선언 시 매개변수 자리에 paylaod 넣어주도록 하자.
(∵ Action Creator 사용하는 컴포넌트에서 리듀서로 보내고자 하는 payload를 인자로 넣어줘야 하기 때문)
3) 리듀서 작성 (initial State, Reducer, export default 생성)
const initialState = {
number: 0,
};
//초기값(객체)--> initialState가 꼭 객체일 필요는 X, 어떤 타입, 값도 가능
// action creator 생성하기
const ADD_NUMBER = "ADD_NUMBER";
const REMOVE_NUMBER = "REMOVE_NUMBER";
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload
};
}
export const removeNumber = (payload) => {
return {
type: REMOVE_NUMBER,
payload
};
};
// Reducer 기본형태
// 리듀서 = 변화를 일으키는 함수, 변화 종류? -> action.type 꾸러미에.
// action.type 별로 case 나눠 switch구문에 활용가능
// reducer 활용 시 중앙 state, 즉 store를 변화시킬 수 있음
// 리듀서는 꼭 return문으로 값을 반환해주기 (중요)
const counter = (state = initialState, action) => {
// 상태를 업데이트하기 위해 액션을 디스패치할 때, 각 액션은 특정한 타입을 가지며, 이 타입을 기반으로 리듀서가 상태를 어떻게 업데이트할지를 결정하게 됨.
switch (action.type) {
case ADD_NUMBER:
return {
number: state.number + action.payload
};
case REMOVE_NUMBER:
return {
number: state.number - action.payload,
};
default:
return state;
}
};
// export default reducer
export default counter;
결과물 - [ App.jsx ]
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { addNumber, removeNumber } from "./redux/modules/counter";
import { useSelector } from "react-redux";
const App = () => {
const counterReducer = useSelector((state) => {
return state.counter;
})
const [input, setInput] = useState('');
const dispatch = useDispatch();
return (
<div>
{counterReducer.number} <br />
<input type="text" value={input} onChange={(e) => {
return setInput(+e.target.value);
}} />
<button
onClick={() => {
dispatch(addNumber(input));
}}
>
+1
</button>
<button
onClick={() => {
dispatch(removeNumber(input));
}}
>
-1
</button>
</div>
);
};
export default App;
Ducks
Ducks 패턴은 Redux 앱을 구성할 때 사용하는 방법론 중 하나
일반적으로 분산되어 있던 액션 타입, 액션 생성자, 리듀서를 하나의 파일로 구성하는 방식을 의미
Redux 관련 코드 관리를 보다 간결하고 모듈화함.
Duck 패턴으로 작성하기
Erik Rasmussen 이 제안한 Ducks 패턴은 아래의 내용을 지켜 모듈을 작성하는 것 입니다.
모듈 파일 1개에 Action Type, Action Creator, Reducer 가 모두 존재하는 작성방식
- Reducer 는 export default.
- Action creator 는 export.
- Action type 은 app/reducer/ACTION_TYPE 형태로 작성
// counter.js (Ducks 패턴을 사용하는 카운터 모듈 예시)
// 1. 액션 타입 정의 (네임스페이스 포함)
const INCREMENT = 'counter/INCREMENT';
const DECREMENT = 'counter/DECREMENT';
// 2. 액션 생성자 정의
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
// 3. 초기 상태 정의
const initialState = {
count: 0,
};
// 4. 리듀서 정의
export default function counterReducer(state = initialState, action) {
switch (action.type) {
case INCREMENT:
return { ...state, count: state.count + 1 };
case DECREMENT:
return { ...state, count: state.count - 1 };
default:
return state;
}
}
(외부 라이브러리로서 사용되거나 외부 라이브러리가 필요로 할 경우, UPPER_SNAKE_CASE 로만 작성해도 ok.)
마무리 - 이젠 이덕수와의 싸움이야.
리액트 유니버스..는 context만 끝내면 좋으련만
props drilling 끝나니 context가 오고
이거 끝내니 이젠 redux가... (아오)
하기 전에 일단.. 주석이랑 코드 정리좀 하고...
강의 정리.. 하고 마무리 지어야지
(격렬하게 미루고싶음 흑흑 선택구현이니까...)
오늘은 빠르게 사라질게욧
구빠5
KPT 회고
- Keep : 벌써 Context까지 구현하다니! 장하다!
- Problem : ...리덕스가 아직 어려워요
- Try : 공부 더 열심히 할게요 ㅜ
ㄲ
ㅡ
ㅅ.
'React TIL' 카테고리의 다른 글
[React] Day_39 데일리 정리 (4) | 2024.11.12 |
---|---|
[React] Day_38 데일리 정리 (4) | 2024.11.11 |
쉬어가는 TIME - 개인과제 중간점검(?) (feat. 꿀팁) (1) | 2024.11.09 |
[React] Day_36 데일리 정리 (4) | 2024.11.08 |
[React] Day_35 데일리 정리 (0) | 2024.11.07 |