헷갈려서 작성한 강의 내용 정리. 파란 형광펜은 확실하게 알지 못하는 부분!
<< 리덕스 >>
💟 Redux | ('State 관리 라이브러리')
💟 Store
◦ createStore() | (Store 만드는 메소드(함수)) | 리덕스 사용시 단 한 번의 호출
◦ 단일 Store로 모든 State tree를 관리)
💟 동작순서
◦ action → dispatch → reducer
💟 modules
◦ state 들의 그룹
💟 reducer
◦ state를 action의 type에 따라 변경하는 함수
◦ input값 2개 : (state, action)
◦ output값 : action.type에 따라 변경된 state
💟 useSelector
◦ store로 접근하는 logic을 가진 'react-redux'의 hook
◦ 컴포넌트에서 store를 직접 조회
💟 dispatch
◦ state 변경에 중요. 해당 컴포넌트에서 중앙저장소Store로 state 변경행동지침 전달
◦ 이벤트가 state변경을 요청했을 때 store로 action객체를 전달하는 역할
1. 리덕스 환경 설정
리액트에서 리덕스 사용할 때 터미널에서 설치할 리덕스 패키지 2개!
#'cd 프로젝트 폴더' in terminal
yarn add redux react-redux
# 위와 같은 의미
yarn add redux
yarn add react-redux # 리덕스를 리액트에서 사용할 수 있게 서로 연결해주는 패키지
2. store 설정 코드 작성
State를 " 전달하기 위한 (to give) " 환경 설정
실행순서
1. 프로젝트의 src 폴더의 하위폴더로 redux폴더 아래 'config 폴더' 생성
2. 'config 폴더'에 'configStore.js' 파일 생성 <--- store 생성
3. 하단 코드 작성
4. 기존에 있던 'index.js' 파일에 하단 코드 작성 <--- 메인페이지에 Store 뿌려주기
./src/config/configStore.js
config 폴더 | 리덕스 설정 관련 파일 전부
configStore.js | store(= 중앙 데이터 관리소 = 중앙 state 관리소) 설정코드 모음 파일
// 📑 configStore.js
import {createStore} from "redux";
import {combineReducers} from "redux"; // 리듀서들을 하나로 묶음
// 중앙 관리소 생성
const rootReducer = combineReducers({ /* modules에 넣어둔 state의 묶음을 key-value 객체형태로 담아*/ });
const store = createStore(rootReducer);
// store 내보내기
export default store
// 📑 index.js
// 원래 있던 코드
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// 📌 redux 설정에 필요한 코드
import store from "./redux/config/configStore";
import { Provider } from "react-redux";
// 원래 있던 코드
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
// 📌 redux 설정에 필요한 코드
// desc : "App을 Provider로 감싸주고, configStore에서 Export default한 store를 넣어준다"
<Provider store={store}>
<App />
</Provider>
)
createStore()
◦ Store 생성
◦ redux 사용시 createStore 호출은 단 한번
combineReducers()
◦ 여러 개의 독립적인 reducer의 반환값(state)을 하나의 상태 객체로 묶는다.
3. 모듈 만들기
State의 그룹 만들기
실행순서
1. 프로젝트의 src 폴더의 하위폴더로 redux폴더 아래 'modules 폴더' 생성
2. 'modules 폴더'에 'counter.js' 파일 생성 <--- state group 생성
// counter App을 만드는 프로젝트라 가정하고 시작
// 📑 .src/modules/counter.js
// 📌 초기 상태값
const initialState = {
number: 0,
};
// 📌 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
// 📌 module file에서는 리듀서를 export default 한다.
export default counter;
※ 가능한 초기 상태값
// 원시데이터, 배열, 객체 다 가능
// 초기값 0
const initialState = 0;
// 초기값이 배열
const initialState = [0];
// 초기값이 number=0, name='짱구'인 객체
const initialState = {
number: 0,
name : "짱구"
}
※ action
reducer 콜백함수의 인자 action은 객체의 형태로 들어옴
객체 action은 type과 value를 가지고 있음.
4. 스토어와 모듈 연결 | 값 조회(읽어오기)
방법 : 컴포넌트에서 useSelector로 스토어를 직접조회
실행순서
0. 조회할 ? 컴포넌트로 이동
1. store에서 꺼낸 값을 할당할 변수 선언
2 useSelector import.
3. useSelector()를 변수에 할당
4. useSelector의 인자에 화살표함수 넣고 // 💬 화살표 함수의 인자에는 'store'가 들어가나? 'state'가 들어가나? store가 state들의 모음집이니?? state보다는 store로 표기하는게?? 낫지 않ㄴ나.... ???
5. 화살표 함수의 인자에서 값을 꺼내 return
import './App.css';
import { useSelector } from 'react-redux'; // 📌 import해오쎄여
function App() {
// 중앙저장소에서 값 읽어오기
// 여기에서 store에 접근하여, counter의 값을 읽어오고 싶다!
// 💙 하단의 state는 중앙저장소 안에 있는 "state"
// 중앙저장소 store에는 state들을 객체형태로 저장하고 있음
// 📌 'useSelector'라는 react-redux hook으로 이 컴포넌트에서 "스토어를 조회"합니다.
const counter = useSelector(state => {
return state.counter;
});
console.log('counter', counter);
return <div>Redux!</div>;
}
export default App;
useSelector(callback함수)
◦ 'react-redux'의 hook
◦ 컴포넌트에서 스토어를 직접 조회
?? useSelector의 콜백함수로 무조건 화살표함수를 써줘야하나?
화살표함수에서 꺼낸 state라는 인자는 현재 프로젝트에 존재하는 모든 리덕스 모듈의 state이다.
'모든 리덕스 모듈의 state'이면 store 아닌ㄴ가..
5. 변경
스토어에 접근해서 데이터(state)를 변경!
실행순서
1. 태그에 이벤트 걸기
2. 이벤트에 함수연결
3. 함수에 'dispatch({~action객체자리~})' 작성
** action 객체에서 key 'type'은 modules폴더의 state정의한 파일 확인!
** action 객체는 type과 payload를 가짐
// 📑 App.jsx 파일
import './App.css';
import { useDispatch, useSelector } from 'react-redux'; // dispatch 쓸라면 import!
function App() {
// 중앙저장소에서 값 읽어오기 (값 조회)
// useSelector
const counter = useSelector(state => {
return state.counter;
});
// for state 변경
// 📌 dispatch를 가져온다.
const dispatch = useDispatch();
return (
<>
<div>현재 카운트: {counter.number}</div>
<button
onClick={() => {
// 📌
// +1을 해주는 logic을 쓴다.
// 이때 logic은 "store에 있는 reducer가 정의해놓은 방식대로 써야함"
// dispatch는 "action 객체"를 store에 던져주니까 인자에 action 객체를 써주면 되고,
// 단, action 객체를 쓸 때 action의 type은 module 폴더에서 state를 정의해줄 때 작성한 거 기준!
// action 객체는 일단 'key-value pair'로 써줌
dispatch({
type: 'PLUS_ONE',
payload: '',
});
}}>
+
</button>
<button
onClick={() => {
dispatch({
type: 'MINUS_ONE',
payload: '',
});
}}>
-
</button>
</>
);
}
export default App;
// 📑 ./src/modules/counter.js
// import { useState } from 'react';
// 초기 상태값(state)
const initialState = {
number: 0,
};
// const [counter, setcounter] = useState(0)
// 리듀서 : 'state에 변화를 일으키는' 함수
// (1) state를 action의 type에 따라 변경하는 함수
// input : state와 action
const counter = (state = initialState, action) => {
console.log('state=> ', state);
switch (action.type) {
// action type은 'PLUS_ONE'
case 'PLUS_ONE':
// return state + 1;
// state가 객체 형태이기 때문에 return 형식 바꿔
// 이 return값으로 update되는 건 local state가 업뎃된게 아니라 리덕스에서 store에 있는 state가 한번 업데이트 됨
return { number: state.number + 1 };
case 'MINUS_ONE':
return { number: state.number - 1 };
default:
return state;
}
};
export default counter;
dispatch 작동 흐름
이벤트 발생하여 state 변경 요청이 들어오면 dispatch가 요청 수행
1. dispatch가 action객체를 가지고 store 방문
(= dispatch는 컴포넌트에서 action객체를 store로 던져주는(보내주는) 역할 )
2. store는 action 객체에 있는 type에 따라 state를 변경해주는 작업
=> action에 있는 type으로 reducer가 state를 제어!
6. redux 사용시 리팩토링
필요성 : 다른 중심 파일에서 할당한 값?를 다른 여러 파일에서 사용할 때 중심파일에 변경사항이 생기면 모든 파일에 반영 시켜야하므로, 하드코딩을 할 경우 변경해야할 부분이 많아서 번거롭고, Human error등이 발생할 경우가 굉장히 높다. 따라서 리팩토링이 필요.... 로직만짜서 컴터에게 맡기자.
1) 같은 값을 변수로
=> 이런 경우 const PLUS_ONE = 'PLUS_ONE'이런 식으로 정의해두고 바로 앞에다가
export const PLUS_ONE = 'PLUS_ONE' 써주기!
다른 파일에서 쓸 때는 import {PLUS_ONE} from '속한 파일 경로'
2) 반복되는 기능은 함수로! | 액션객체를 만들어주는 함수
7. payload
전달되는 실체.
(예를 들어서 input값으로 사용자가 지정한 값부터 counter를 시작하고 싶을 때!)
action 객체가 활용되는 방법.
action 객체에는 type이 있고, payload가 있다.
type에 따라서 state를 어떻게 연산할지 결정하고, payload만큼을 type에 맞게 처리한다.
"dispatch가 action 객체를 store로 던진다!!!" 꼭 기억하기
8. 마무리 ... Ducks 패턴
이 페이지에서 보았듯이 redux를 사용하려면 구성요소 이것저것을 많이 만들어댄다...
=> 개발자들의 특성마다 다를 수 있고 => 협업시 중구난방될 가능성 UPPPP
이를 방지하기 위해 Erik Rasmussn이 Ducks 패턴라는걸 제안해주었답니다
Ducks 패턴
1. Reducer함수를 export default 한다.
2. Action creator 함수들을 export한다.
3. Action type은 app/reducer/ACTION_TYPE 형태로 작성
(외부 라이브러리로서 사용될 경우 또는 외부 라이브러리가 필요할 경우에는 UPPER_SNAKE_CASE로만 작성해도 괜츈)
그래서 모듈파일 1개에 Action Type, Action Creator, Reducer가 모두 존재하는 작성방식