Redux
- 리덕스를 사용하면 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜서
더욱 효율적으로 관리 할 수 있으며 글로벌 상태 관리도 손쉽게 할 수 있다.
- 상태값을 컴포넌트에 종속시키지 않고, 상태 관리를 컴포넌트의 바깥에서 관리 할 수 있게 된다.
- Redux와 React는 독립적으로 사용될 수 있는 별개의 다른 라이브러리이다.
- Redux는 자바스크립트 어플리케이션에서 흔히 쓰이는 상태관리 라이브러리이다.
- Redux는 Angular, Vue, Ember, jQuery 또는 Vanilla JavaScript와 같은 다른 라이브러리,
프레임워크에서도 사용할 수 있다
원래는 부모가 상태변수를 다 쥐고있기 때문에 부모를 통해서 전달전달하는 props 방식을 썼다.
이제는 Store다 해서 따로 데이터를 관리하자는 것이다.
컴포넌트랑 데이터를 분리해서 관리하자 !! 그럼 Reducer가 필요하다.
나 데이터 바꼈어 ! 라는 사실을 Store한테 통보하면 Store가 데이터를 쫙 다시 전달해준다.
우유배달 신청할 때 먼저 우유배급소한테 신청을 한다.
아침에 우유가 들어오면 나한테 신청서를 보낸 사람들한테 우유를 전달해주는 것 같은 거다.
store
- 모두 한 곳에서 집중 관리
- 컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담는다.
- 컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.
action
- Action(액션)은 앱에서 스토어에 운반할 데이터를 말한다. (주문서)
- Action(액션)은 자바스크립트 객체 형식으로 되어있다.
reducer
- Action(액션)을 Store(스토어)에 바로 전달하는 것이 아니다.
- Action(액션)을 Reducer에 전달해야한다.
- Reducer가 주문을 보고 Store의 상태를 업데이트하는 것이다.
- Action을 Reducer에 전달하기 위해서는 dispatch() 메소드를 사용해야 한다.
① Action(액션) 객체가 dispatch() 메소드에 전달된다.
② dispatch(액션)를 통해 Reducer를 호출한다.
③ Reducer는 새로운 Store를 생성한다.
day08
[설치]
npm install react-redux ( yarn add react-redux / npm i react-redux ) --- 3개 중에 하나 !!
npm i redux-devtools-extension ( yarn add redux-devtools-extension ) -- 여기서는 npm은 에러뜨더라 !
난 빨간색으로 했다 !!
npm start
yarn add redux -- 안되가지구 얘도 깔았다 !!
day08
public
index.html (화면에 띄워지는 부분)
src
App.js
index.js (App에 있는 내용을 꺼내다가 root에 렌더링한다.) (id값 root를 찾아라 -- index.html)
components (디렉토리)
store (디렉토리)
index.js
import { Provider } from 'react-redux';
<Provider store={ store }>
<App />
</Provider>
index.js에 store가 있다는 것을 알려줄 것이다 !!
Provider(store)가 App에게 공급한다는 거 알려주기
<App />의 후손까지 모두 내가 제공한 store을 사용해도 된다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
//-----------------------------------
import { Provider } from 'react-redux';
//-----------------------------------
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={ store }>
<App /> {/* <App />의 후손까지 모두 내가 제공한 store을 사용해도 된다. */}
</Provider>
</React.StrictMode>
);
// 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();
createStore
앱의 상태 트리 전체를 보관하는 Redux 저장소를 만든다.
앱 내에는 단 하나의 저장소만 있어야 한다.
앱에 하나 이외의 저장소를 만들지 않는다.
-> 대신 여러 개의 리듀서를 하나의 루트 리듀서로 만들기 위해 combineReducers를 사용한다.
반환
앱의 전체 상태를 가지고 있는 객체이다.
이 객체의 상태를 바꾸는 유일한 방법은 액션을 보내는 것뿐이다.
UI를 업데이트하기 위해 상태를 구독 할 수도 있다.
//import { creatStore } from 'redux'; - deprecated(더 이상 사용되지 않는다는 뜻) , 실행은 된다.
import { legacy_createStore as creatStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
(개발자도구에서 확인하면서 갈거다 하면)
const store = creatStore(rootReducer, composeWithDevTools);
const store = creatStore(rootReducer); -- 개발자 도구 툴 안 쓸거면 이렇게 선언
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
//-----------------------------------
import { Provider } from 'react-redux';
import rootReducer from './store';
//import { creatStore } from 'redux'; - deprecated, 실행은 된다.
import { legacy_createStore as creatStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
//const store = creatStore(rootReducer);
const store = creatStore(rootReducer, composeWithDevTools());
//-----------------------------------
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={ store }>
<App /> {/* <App />의 후손까지 모두 내가 제공한 store을 사용해도 된다. */}
</Provider>
</React.StrictMode>
);
// 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();
day08
public
index.html (화면에 띄워지는 부분)
src
App.js
index.js
components (디렉토리)
Color.jsx
store (디렉토리)
App.js
import React from 'react';
import Color from './components/Color';
const App = () => {
return (
<div>
<Color/>
</div>
);
};
export default App;
Color.jsx
이제는 color 상태변수 선언을 여기서 안 할 거다 !!
import React from 'react';
const Color = () => {
const color = ~~~;
const dispatch = useDispatch();
return (
<div>
<h1 style={{ color: color }}>컬러 : { color }</h1>
<p>
<button onClick={ () => dispatch(red()) }>RED</button>
<button onClick={ () => dispatch(green()) }>GREEN</button>
<button onClick={ () => dispatch(blue()) }>BLUE</button>
<button onClick={ () => dispatch(magenta()) }>MAGENTA</button>
</p>
</div>
);
};
export default Color;
day08
public
index.html (화면에 띄워지는 부분)
src
App.js
index.js
components (디렉토리)
Color.jsx
store (디렉토리)
modules (디렉토리)
color.jsx (첫글자 소문자, 컴포넌트 X)
color.jsx
컴포넌트가 아니라 순수 JS(자바스크립트) 파일이다.
1. 액션 생성
//1. 액션 생성
//모듈이름을 앞에 붙여주어서 액션명 중복 방지
const RED = 'color/RED';
const GREEN = 'color/GREEN';
const BLUE = 'color/BLUE';
const MAGENTA = 'color/MAGENTA';
저기있는 color는 모듈이름인거 확인 !!!
2. 액션 보내기
//2. 액션 보내기
export const red = () => ({ type: RED })
export const green = () => ({ type: GREEN })
export const blue = () => ({ type: BLUE })
export const magenta = () => ({ type: MAGENTA })
3. 초기값
//3. 초기값
const initialState = { color: 'hotpink' }
4. 리듀서 만들기 - state, action 파라메터를 참조하여, 새로운 상태 객체를 만든다.
state - 현재상태, action - 액션 객체(위에서 잡아준 거)
반드시 state에는 초기값을 주어야 한다.
//4. 리듀서 만들기 - state, action 파라메터를 참조하여, 새로운 상태 객체를 만든다.
// state - 현재상태, action - 액션 객체(위에서 잡아준 거)
// 반드시 state에는 초기값을 주어야 한다.
const reducer = (state=initialState, action) => {
switch(action.type){
case RED:
return { color: red }
case GREEN:
return { color: green }
case BLUE:
return { color: blue }
case MAGENTA:
return { color: magenta }
default:
return state //반드시 default는 작성해야한다.
}
}
전체코드
//1. 액션 생성
//모듈이름을 앞에 붙여주어서 액션명 중복 방지
const RED = 'color/RED';
const GREEN = 'color/GREEN';
const BLUE = 'color/BLUE';
const MAGENTA = 'color/MAGENTA';
//2. 액션 보내기
export const red = () => ({ type: RED })
export const green = () => ({ type: GREEN })
export const blue = () => ({ type: BLUE })
export const magenta = () => ({ type: MAGENTA })
//3. 초기값
const initialState = { color: 'hotpink' }
//4. 리듀서 만들기 - state, action 파라메터를 참조하여, 새로운 상태 객체를 만든다.
// state - 현재상태, action - 액션 객체(위에서 잡아준 거)
// 반드시 state에는 초기값을 주어야 한다.
const reducer = (state=initialState, action) => {
switch(action.type){
case RED:
return { color: red }
case GREEN:
return { color: green }
case BLUE:
return { color: blue }
case MAGENTA:
return { color: magenta }
default:
return state //반드시 default는 작성해야한다.
}
}
export default reducer //컴포넌트가 아니라 순수 JS(자바스크립트) 파일이다.
//액션생성에서
이벤트가 발생되는건 'color/RED'인데 너무 길어서 RED로 적은거다 !!
Color.jsx에서
원래는 const [color, setColor - = useState('hotponk')
이렇게 했는데 그래버리면 이 컴포넌트에 종속이되어버리므로
저걸 빼버리고
우리의 Store(color.jsx)에 color라는 변수를 따로 놨두는 것이다.
그러므로 Color.jsx가 color 변수를 가지고 있는 것이 아니다.
그러므로 const color = useSelector(state => state.color.color); 이 color라는 모듈에 있어요 하는 것이다.
index.jsx에서
combineReducer가 부모 리듀서이고
color : color 얘가 자식 리듀서
위에 color가 바로 이 color이다.
RED를 누르면 dispatch해서 red 함수를 부른다.
그럼
color.jsx에서
액션 보내기에서 red함수는 type: RED다 하고
리듀서 만들기에서 type:RED이면 color: 'red'다 해서 return하는 것이다.
day08
public
index.html (화면에 띄워지는 부분)
src
App.js
index.js
components (디렉토리)
Color.jsx
store (디렉토리)
index.jsx ( Color.jsx와 color.jsx를 연결해주는 역할)
modules (디렉토리)
color.jsx (첫글자 소문자, 컴포넌트 X)
createStore
앱의 상태 트리 전체를 보관하는 Redux 저장소를 만든다.
앱 내에는 단 하나의 저장소만 있어야 한다.
앱에 하나 이외의 저장소를 만들지 않는다.
-> 대신 여러 개의 리듀서를 하나의 루트 리듀서로 만들기 위해 combineReducers를 사용한다.
index.jsx
import { combineReducers } from "redux";
import color from './modules/color.jsx';
export default combineReducers({
color : color // 이름(변수명) : 리듀서 이름
})
첫 번째 color는 reducer의 이름이고
두 번째 color는 내가 액션으로 내보낸 color이다.
Color.jsx
import { red, green, blue, magenta } from '../store/modules/color.jsx';
변수의 위치를 알려줘야 쓸 수 있다.
const color = useSelector(state => state.color.color);
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { red, green, blue, magenta } from '../store/modules/color.jsx';
const Color = () => {
const color = useSelector(state => state.color.color);
const dispatch = useDispatch();
return (
<div>
<h1 style={{ color: color }}>컬러 : { color }</h1>
<p>
<button onClick={ () => dispatch(red()) }>RED</button>
<button onClick={ () => dispatch(green()) }>GREEN</button>
<button onClick={ () => dispatch(blue()) }>BLUE</button>
<button onClick={ () => dispatch(magenta()) }>MAGENTA</button>
</p>
</div>
);
};
export default Color;
이거의 위치를 알 수 있다.
함수를 통해서 color 각각을 가져오는 것이다.
https://chromewebstore.google.com/?hl=ko
index.jsx
여기서 똑같은 값이 두 개면 하나로 퉁칠 수도 있다.
import { combineReducers } from "redux";
import color from './modules/color.jsx';
export default combineReducers({
color//color : color //이름(변수명) : 리듀서 이름
})
하지만 헷갈리면 원래 방식으로 !!
day08
public
index.html (화면에 띄워지는 부분)
src
App.js
index.js
components (디렉토리)
Color.jsx
Count.jsx
store (디렉토리)
index.jsx
modules (디렉토리)
color.jsx (첫글자 소문자, 컴포넌트 X)
count.jsx
App.js
import React from 'react';
import Color from './components/Color';
import Count from './components/Count';
const App = () => {
return (
<div>
<Color/>
<hr/>
<Count/>
</div>
);
};
export default App;
Count.jsx
import React from 'react';
const Count = () => {
const count = ~~~;
const dispatch = ~~~~;
return (
<div>
<h1>카운트 : { count }</h1>
<p>
<button onClick={ () => dispatch(increment())}>증가</button>
<button onClick={ () => dispatch(decrement())}>감소</button>
<button onClick={ () => dispatch(reset())}>초기화</button>
</p>
</div>
);
};
export default Count;
아직은 count dispatch 다 내가 안 갖고 있다 !!
count.jsx
1. 액션 생성
//1. 액션 생성
//모듈이름을 앞에 붙여주어서 액션명 중복 방지
const INCREMENT = 'count/INCREMENT';
const DECREMENT = 'count/DECREMENT';
const RESET = 'count/RESET';
이름이 너무 기니까 그래서 이름을 INCREMENT로 명시를 하는 것이다 !!
얘가 정의하는 애들은 여기 안에서만 쓰겠다는 것이다 !!
const GREEN = 'color/GREEN'; 이렇게 액션을 생성해도
실제 액션명은 'color/GREEN' 이렇게 되어있는거 볼 수 있음
말그대로 여기서만 쓰려고 INCREMENT 한 것과 같은 것이다.
2. 액션 보내기
//2. 액션 보내기
export const increment = () => ({ type: INCREMENT })
export const decrement = () => ({ type: DECREMENT })
export const reset = () => ({ type: RESET })
type에는 위에서 생성한 액션명을 적어주는 것이다.
각각의 액션을 increment decrement ~~ 다 해서 내보내준다.
3. 초기값
//3. 초기값
const initialState = { count: 0 }
count를 0으로 상태변수 정의를 해준다.
이 상태변수의 값을 어떤 이벤트냐에 따라 값을 바꿔줘야한다.
바꿔주러 4번으로 가자 !!
4. 리듀서 만들기
//4. 리듀서 만들기 - state, action 파라메터를 참조하여, 새로운 상태 객체를 만든다.
// state - 현재상태, action - 액션 객체(위에서 잡아준 거)
// 반드시 state에는 초기값을 주어야 한다.
const reducer = (state=initialState, action) => {
switch(action.type){
case INCREMENT:
return { count: state.count+ 1 }
case DECREMENT:
return { count: state.count - 1 }
case RESET:
return { count: 0 }
default:
return state //반드시 default는 작성해야한다.
}
}
export default reducer //컴포넌트가 아니라 순수 JS(자바스크립트) 파일이다.
정의해놓은 action.type에 따라 case별로 count 상태변수 값 바꾸기 !!
count는 그 값을 state가 기본값을 가지고 있기 때문에
state가 쥐고있는 count의 값을 바꿔라 state.count + 1 이런식으로 적어줘야한다.
index.jsx
import { combineReducers } from "redux";
import color from './modules/color.jsx';
import count from './modules/count.jsx';
export default combineReducers({
color : color, //이름(변수명) : 리듀서 이름
count : count
})
combineReducers는 여러 리듀서들의 부모 리듀서이다.
여러 리듀서를 관리해주는 역할을 한다.
count : count이면 count 하나로 적어줄 수 있다.
Count.jsx
const count = useSelector(state => state.count.count);
리듀서명이 count이고 상태변수 값은 count.jsx에 있다.
const dispatch = useDispatch();
이렇게 적어주고
dispatch(increment())} 이 함수들이 어디서 왔는지를 적어줘야한다.
import { increment, decrement, reset } from '../store/modules/count';
여기서 왔다는 것을 적어줘야한다.
import React from 'react';
import { increment, decrement, reset } from '../store/modules/count';
import { useDispatch, useSelector } from 'react-redux';
const Count = () => {
const count = useSelector(state => state.count.count);
const dispatch = useDispatch();
return (
<div>
<h1>카운트 : { count }</h1>
<p>
<button onClick={ () => dispatch(increment())}>증가</button>
<button onClick={ () => dispatch(decrement())}>감소</button>
<button onClick={ () => dispatch(reset())}>초기화</button>
</p>
</div>
);
};
export default Count;
App.jsx store
Color.jsx Count.jsx module
각각의 자식으로 잡고있기 때문에 서로 데이터를 공유할 수가 없다. color.jsx
count.jx
따로 store를 잡아줬기 때문에 Color.jsx에서 count.jsx도 사용할 수 있따.
Count.jsx
const count = useSelector(state => state.count.count);
<h1 style={{ color: color }}>컬러 : { color }, 카운트 : { count }</h1>
같이 움직이는 거 볼 수 있다.
언제든지 원하면 store에서 갖다쓸 수 있다 !!
day08
public
index.html (화면에 띄워지는 부분)
src
App.js
index.js
components (디렉토리)
Color.jsx
Count.jsx
Animal.jsx
store (디렉토리)
index.jsx
modules (디렉토리)
color.jsx
count.jsx
animal.jsx
Animal.jsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
const Animal = () => {
const name = ~~~;
const crying = ~~~;
const dispatch = ~~~;
return (
<div>
<h1>동물의 울음소리</h1>
<h1>{ name }는(은) { crying }</h1>
<p>
<button onClick={ () => dispatch(tiger()) }>호랑이</button>
<button onClick={ () => dispatch(dog()) }>강아지</button>
<button onClick={ () => dispatch(cat()) }>고양이</button>
<button onClick={ () => dispatch(chick()) }>병아리</button>
</p>
</div>
);
};
export default Animal;
animal.jsx
1. 액션 생성
//1. 액션 생성
//모듈이름을 앞에 붙여주어서 액션명 중복 방지
const TIGER = 'animal/TIGER';
const DOG = 'animal/DOG';
const CAT = 'animal/CAT';
const CHICK = 'animal/CHICK';
2. 액션 보내기
//2. 액션 보내기
export const tiger = () => ({ type: TIGER })
export const dog = () => ({ type: DOG })
export const cat = () => ({ type: CAT })
export const chick = () => ({ type: CHICK })
3. 초기화
//3. 초기값
const initialState = {
name: '돼지',
crying: '꿀꿀'
}
4. 리듀서 만들기
//4. 리듀서 만들기 - state, action 파라메터를 참조하여, 새로운 상태 객체를 만든다.
// state - 현재상태, action - 액션 객체(위에서 잡아준 거)
// 반드시 state에는 초기값을 주어야 한다.
const reducer = (state=initialState, action) => {
switch(action.type){
case TIGER:
return { name: '호랑이', crying: '어흥' }
case DOG:
return { name: '강아지', crying: '멍멍' }
case CAT:
return { name: '고양이', crying: '야옹' }
case CHICK:
return { name: '병아리', crying: '삐약삐약' }
default:
return state //반드시 default는 작성해 야한다.
}
}
export default reducer //컴포넌트가 아니라 순수 JS(자바스크립트) 파일이다.
index.jsx
import { combineReducers } from "redux";
import color from './modules/color.jsx';
import count from './modules/count.jsx';
import animal from './modules/animal.jsx';
export default combineReducers({
color : color, //이름(변수명) : 리듀서 이름
count : count,
animal : animal
})
Animal.jsx
import { useDispatch, useSelector } from 'react-redux';
import { tiger, dog, cat, chick } from '../store/modules/animal.jsx';
const name = useSelector(state => state.animal.name);
const crying = useSelector(state => state.animal.crying);
const dispatch = useDispatch();
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { tiger, dog, cat, chick } from '../store/modules/animal.jsx';
const Animal = () => {
const name = useSelector(state => state.animal.name);
const crying = useSelector(state => state.animal.crying);
const dispatch = useDispatch();
return (
<div>
<h1>동물의 울음소리</h1>
<h1>{ name }는(은) { crying }</h1>
<p>
<button onClick={ () => dispatch(tiger()) }>호랑이</button>
<button onClick={ () => dispatch(dog()) }>강아지</button>
<button onClick={ () => dispatch(cat()) }>고양이</button>
<button onClick={ () => dispatch(chick()) }>병아리</button>
</p>
</div>
);
};
export default Animal;
day08
public
index.html (화면에 띄워지는 부분)
src
App.js
index.js
components (디렉토리)
Color.jsx
Count.jsx
Animal.jsx
CoffeeOrder.jsx
CoffeeResult.jsx
store (디렉토리)
index.jsx
modules (디렉토리)
color.jsx
count.jsx
animal.jsx
coffee.jsx
상태변수 : name. qty
아메리카노, 카페모카, 바닐라라떼 개수를 치면
영수증 찍기
아메리카노 2잔 3000
카페모카 1잔 2500
바닐라라뗴 3잔 6000원
총 합계 11500원
개수가 0인 것은 영수증에 출력되지않는다.
App.js
import React from 'react';
import Color from './components/Color';
import Count from './components/Count';
import Animal from './components/Animal';
import CoffeeOrder from './components/CoffeeOrder';
import { useSelector } from 'react-redux';
import CoffeeResult from './components/CoffeeResult';
const App = () => {
const ordered = useSelector(state => state.coffee.ordered);
return (
<div>
{/* <Color/> */}
{/* <hr/> */}
{/* <Count/> */}
{/* <hr/> */}
{/* <Animal/> */}
<CoffeeOrder/>
{ordered && <CoffeeResult />}
</div>
);
};
export default App;
CoffeeOrder.jsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setAmericano, setMoca, setVanilla, setOrder } from '../store/modules/coffee.jsx';
import styles from '../css/CoffeeOrder.module.css';
const CoffeeOrder = () => {
const americano = useSelector(state => state.coffee.americano);
const moca = useSelector(state => state.coffee.moca);
const vanilla = useSelector(state => state.coffee.vanilla);
const dispatch = useDispatch();
const onOrder = () => {
dispatch(setOrder());
};
const onReset = () => {
dispatch(setAmericano(0));
dispatch(setMoca(0));
dispatch(setVanilla(0));
};
return (
<div className={styles.orderContainer}>
<h1 className={styles.title}>주문표</h1>
<div className={styles.inputGroup}>
아메리카노
<input type='number' className={styles.input} value={americano}
onChange={ (e) => dispatch(setAmericano(e.target.value)) }
/>
</div>
<div className={styles.inputGroup}>
카페모카
<input type='number' className={styles.input} value={moca}
onChange={ (e) => dispatch(setMoca(e.target.value)) }
/>
</div>
<div className={styles.inputGroup}>
바닐라라떼
<input type='number' className={styles.input} value={vanilla}
onChange={ (e) => dispatch(setVanilla(e.target.value)) }
/>
</div>
<div className={styles.buttonContainer}>
<button className={styles.button} onClick={ onOrder }>주문하기</button>
<button className={styles.button} onClick={ onReset }>초기화</button>
</div>
</div>
);
};
export default CoffeeOrder;
coffee.jsx
1. 액션 생성
//1. 액션 생성
//모듈이름을 앞에 붙여주어서 액션명 중복 방지
const AMERICANO = 'coffee/AMERICANO';
const MOCA = 'coffee/MOCA';
const VANILLA = 'coffee/VANILLA';
const ORDER = 'coffee/ORDER';
2. 액션 보내기
//2. 액션 보내기
export const setAmericano = (qty) => ({ type: AMERICANO, payload: qty });
export const setMoca = (qty) => ({ type: MOCA, payload: qty });
export const setVanilla = (qty) => ({ type: VANILLA, payload: qty });
export const setOrder = () => ({ type: ORDER });
3. 초기값
//3. 초기값
const initialState = {
americano: 0,
moca: 0,
vanilla: 0,
price: { americano: 1500, moca: 2500, vanilla: 2000 },
ordered: false,
};
4. 리듀서 만들기
//4. 리듀서 만들기 - state, action 파라메터를 참조하여, 새로운 상태 객체를 만든다.
// state - 현재상태, action - 액션 객체(위에서 잡아준 거)
// 반드시 state에는 초기값을 주어야 한다.
const reducer = (state = initialState, action) => {
switch(action.type){
case AMERICANO:
return { ...state, americano: action.payload };
case MOCA:
return { ...state, moca: action.payload };
case VANILLA:
return { ...state, vanilla: action.payload };
case ORDER:
return { ...state, ordered: true };
default:
return state;
}
};
export default reducer //컴포넌트가 아니라 순수 JS(자바스크립트) 파일이다.
CoffeeOrder.jsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setAmericano, setMoca, setVanilla, setOrder } from '../store/modules/coffee.jsx';
import styles from '../css/CoffeeOrder.module.css';
const CoffeeOrder = () => {
const americano = useSelector(state => state.coffee.americano);
const moca = useSelector(state => state.coffee.moca);
const vanilla = useSelector(state => state.coffee.vanilla);
const dispatch = useDispatch();
const onOrder = () => {
dispatch(setOrder());
};
const onReset = () => {
dispatch(setAmericano(0));
dispatch(setMoca(0));
dispatch(setVanilla(0));
};
index.jsx
import { combineReducers } from "redux";
import color from './modules/color.jsx';
import count from './modules/count.jsx';
import animal from './modules/animal.jsx';
import coffee from './modules/coffee.jsx';
export default combineReducers({
color : color, //이름(변수명) : 리듀서 이름
count : count,
animal : animal,
coffee : coffee
})
CoffeeResult.jsx
import React from 'react';
import { useSelector } from 'react-redux';
import styles from '../css/CoffeeResult.module.css';
const CoffeeResult = () => {
const { americano, moca, vanilla, price } = useSelector(state => state.coffee);
const total = (americano * price.americano) + (moca * price.moca) + (vanilla * price.vanilla);
return (
<div className={styles.resultContainer}>
<h1 className={styles.resultTitle}>영수증</h1>
<div className={styles.resultItem}>
<span style={{ fontWeight: 'bold' }}>커피</span>
<span style={{ fontWeight: 'bold' }}>수량</span>
<span style={{ fontWeight: 'bold' }}>가격</span>
</div>
<hr />
{americano > 0 && (
<div>
<div className={styles.resultItem}>
<span style={{ fontWeight: 'bold' }}>아메리카노</span>
<span>{americano}잔</span>
<span>{(americano * price.americano).toLocaleString()}원</span>
</div>
<hr />
</div>
)}
{moca > 0 && (
<div>
<div className={styles.resultItem}>
<span style={{ fontWeight: 'bold' }}>카페모카</span>
<span>{moca}잔</span>
<span>{(moca * price.moca).toLocaleString()}원</span>
</div>
<hr />
</div>
)}
{vanilla > 0 && (
<div>
<div className={styles.resultItem}>
<span style={{ fontWeight: 'bold' }}>바닐라라떼</span>
<span>{vanilla}잔</span>
<span>{(vanilla * price.vanilla).toLocaleString()}원</span>
</div>
<hr />
</div>
)}
{total > 0 && (
<strong>
<p className={styles.resultItem2}>총 합계 {total.toLocaleString()}원</p>
</strong>
)}
</div>
);
};
export default CoffeeResult;
[ 추가 설명 ]
Redux란 무엇인가?
Redux(리덕스)란 JavaScript(자바스트립트) 상태관리 라이브러리이다.
Redux를 사용하는 이유
먼저 상태란?
- React에서 State는 component 안에서 관리되는 것이다.
- 자식 컴포넌트들 간의 다이렉트 데이터 전달은 불가능 하다.
- 자식 컴포넌트들 간의 데이터를 주고 받을 때는 상태를 관리하는 부모 컴포넌트를 통해서 주고 받는다.
- 그런데 자식이 많아진다면 상태 관리가 매우 복잡해진다.
- 상태를 관리하는 상위 컴포넌트에서 계속 내려 받아야한다. => Props drilling 이용해서
리덕스를 쓰면, 상태 관리를 컴포넌트 바깥에서 한다!
리덕스를 사용하면 상태값을, 컴포넌트에 종속시키지 않고, 상태 관리를 컴포넌트의 바깥에서 관리 할 수 있게 된다.
1. 전역 상태 저장소 제공
2. Props drilling 이슈 해결
이런 흐름으로 진행되는 것이다.
Store (스토어)
Store(스토어)는 상태가 관리되는 오직 하나의 공간이다.
- 컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담는다.
- 컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.
Action (액션)
- Action(액션)은 앱에서 스토어에 운반할 데이터를 말한다. (주문서)
- Action(액션)은 자바스크립트 객체 형식으로 되어있다.
Reducer (리듀서)
- Action(액션)을 Store(스토어)에 바로 전달하는 것이 아니다.
- Action(액션)을 Reducer(리듀서)에 전달해야한다.
- Reducer(리듀서)가 주문을 보고 Store(스토어)의 상태를 업데이트하는 것이다.
- Action(액션)을 Reducer(리듀서)에 전달하기 위해서는 dispatch() 메소드를 사용해야한다.
Action(액션) 객체가 dispatch() 메소드에 전달된다.
dispatch(액션)를 통해 Reducer를 호출한다.
Reducer는 새로운 Store 를 생성한다.
예를 들어서 B 에서 일어나는 변화가 G 에 반영된다고 가정을 해보자.
리덕스를 프로젝트에 적용하게 되면 이렇게 스토어가 생긴다.
스토어 안에는 프로젝트의 상태에 관한 데이터들이 담겨있다.
G 컴포넌트는 스토어에 구독을 한다. 구독을 하는 과정에서, 특정 함수가 스토어한테 전달이 된다.
그리고 나중에 스토어의 상태값에 변동이 생긴다면 전달 받았던 함수를 호출해준다.
이제 B 컴포넌트에서 어떤 이벤트가 생겨서, 상태를 변화 할 일이 생긴다.
이 때 dispatch 라는 함수를 통하여 액션을 스토어한테 던져준다.
액션은 상태에 변화를 일으킬 때 참조 할 수 있는 객체다.
액션 객체는 필수적으로 type이라는 값을 가지고 있어야 한다.
액션 객체를 받으면 전달받은 액션의 타입에 따라 어떻게 상태를 업데이트 해야 할지 정의를 해줘야 한다.
이러한 업데이트 로직을 정의하는 함수를 리듀서라고 부른다.
리듀서 함수는 두가지의 파라미터를 받습니다.
- state: 현재 상태
- action: 액션 객체
그리고, 이 두가지 파라미터를 참조하여, 새로운 상태 객체를 만들어서 이를 반환합니다.
상태에 변화가 생기면, 이전에 컴포넌트가 스토어한테 구독 할 때 전달해줬었던 함수 listener 가 호출된다.
이를 통하여 컴포넌트는 새로운 상태를 받게되고, 이에 따라 컴포넌트는 리렌더링을 한다.
1. Redux Store가 저장할 상태 정보(State) 즉, Redux Store는 내 상태 정보를 저장한다.
2. 그리고 새 상태를 반환하는 함수인 리듀서(Reducer)를 사용한다.
즉, 전역적으로 상태를 관리할 데이터 컨테이너인 store를 만들 것이며,
여기서 관리할 상태 데이터 타입을 선언하고, 상태 데이터를 새로 바꾸기 위한 리듀서를 만들어야 한다.
[개념1] useSelector - 데이터 읽기
- store에 저장된 내용을 read하기 위해서는 useSelector훅을 사용한다.
useSelector훅은 리덕스에서 자체 제공한다.
[개념 2] dispatch - 데이터 수정
- redux의 기본 제공 hook으로, useSelector가 state값을 read하기 위해서 사용되었다면,
이 hook은 state값을 변경하기 위해서 사용된다. - useDispatch hook이 state를 변경할 때에는 Action이 사용된다.
Action을 리덕스 store에 전달 할 때는 reducer를 통해서 가게 되는데,
위로 가서 reducer 선언문에 보면 switch 문으로 각 action이 전달되었을 때 동작을 정의해놓은 것이다. - redux에서 제공해주는 useDispatch hook을 호출하면 바로 dispatch()가 사용이 가능하다.
'REACT' 카테고리의 다른 글
DAY 82 - React - Context / 마지막 실습(HOMEWORK) (2024.11.01) (2024.11.04) (1) | 2024.11.02 |
---|---|
DAY 80 - Spring + React +MyBatis(MySQL) - 글 상세보기 (2024.10.30) (0) | 2024.10.30 |
DAY 79, 80 - Spring + React +MyBatis(MySQL) HOMEWORK - 글쓰기 / 글목록 (2024.10.29) (2024.10.30) (0) | 2024.10.30 |
DAY 79 - Spring + React +MyBatis(MySQL) (2024.10.29) (0) | 2024.10.30 |
DAY 78 - React HOMEWORK (2024.10.28) (0) | 2024.10.28 |