참고
2022.11.18 - [React] - useReducer
2022.07.07 - [React] - UseContext(Context API)
Redux는 무엇인지?
- cross-component 또는 app-wide state를 위한 state관리 시스템
- A predictable state container for Javascript apps
- 자바스크립트로 만든 어플리케이션을 위한 예측 가능한 상태의 저장소
- 예측 가능
- 1) 코드의 복잡성을 낮추어, 코드가 예측 가능하게 만들어 줌
- Single Source of Truth
- 상태는 객체임 state={}
- 하나의 객체안에 app에서 필요한 모든 데이터를 다 넣는다
- 2) 외부로 부터 철저히 차단시킨다
- 인가된 담당자(함수)만 사용할 수 있음. dispatcher 혹은 reducer만 쓸 수 있게
- 직접 state값을 가지고 오지 못함. 함수를 통해서만 가져갈 수 있음
- 예기치 않게 state값이 바뀌는 문제를 사전에 차단한다
- 3) state의 데이터를 사용하는 부품들에 연락하여 지령을 내림
- 자기 할일만 하면 됨
- 1) 코드의 복잡성을 낮추어, 코드가 예측 가능하게 만들어 줌
- 장점
- 1.undo, redo가 쉬움
- state값을 복제하고, 그 복제값만 사용하기 때문
- 2.debugger를 통해 현재 상태 뿐만아니라 이전 상태도 확인 할 수 있음
- 3.module reloading을 할 수 있음
- 1.undo, redo가 쉬움
- 예측 가능
state의 종류
- Local State: 하나의 컴포넌트에 속하는 state
- eg. 사용자의 입력 값을 감지하는 input 창, '더보기'를 할 수 있는 토글
- => 이는 컴포넌트 내부에서 useState(), useReducer()로 관리
- Cross-Component State: 여러 개의 컴포넌트에 영향을 미치는 state
- eg. 모달
- => 이는 "prop chains", "prop drilling"이 필요
- App-wide state: 모든 앱(거의 모든 컴포넌트)에 영향을 미치는 state
- eg. 사용자 인증 (로그인)
- => 이는 "prop chains", "prop drilling"이 필요
- cross-component state, app-wide state은 prop을 관리하기 번거로우므로 내장 hook인 useContext를 사용할 수 있음
- redux도 동일한 문제를 해결해 줌
Redux vs. useContext
그렇다면 useContext를 사용하면 되지 왜 redux가 필요한가?
2022.07.07 - [React] - UseContext(Context API)
useContext의 잠재적인 단점
- 1.큰 앱에서는 설정이 복잡해지고, 상태관리가 어려울 수 있음
- 1.1.여러 state가 있으면 그걸 관리하기위한 여러 context가 생기고, 그럼 ContextProvider컴포넌트가 많아짐.
- 결국 심하게 중첩된 JSX코드가 나올 수 있음
-
- 1.2.큰 ContextProvider를 사용해서 그 안에서 관리할 수도 있지만 그럼 하나의 ContextProvider 많을 것을 관리하기 때문에 유지하기 어려움
- 2. 성능
- useContext는 테마를 변경하거나, 인증 같은 '저빈도 업데이트'에는 좋지만, 데이터가 자주 변경되는 경우에는 적합하지 않음
- 따라서 유동적인 상태 관리 라이브러리인 redux가 적합
Redux를 왜 쓰는지?
1. props의 대체제
2. state관리하기 쉬움
1. props의 대체제
- 컴포넌트로 만들어진 리액트에서, state(변수)를 쓰고싶을 때, 다른 컴포넌트들은 이 state를 못씀
- 꼭 props로 전송해 주어야함 (props를 한번에 전송하는 문법은 없음)
- 2022.08.18 - [React] - Props
- =>이럴 때 redux사용
- redux사용하면 state를 보관하는 파일을 만들 수 있음
- =>모든 컴포넌트들이 이 state을 직접 빼다가 쓸 수 있음 props를 사용할 필요가 없어짐
//index.js 세팅 + state보관
import {Provider} from 'react-redux';
import {createStore} from 'redux';
const 체중 = 100; //state마음대로 보관 가능
function reducer(state = 체중, action){
return state
}
let store = createStore(reducer)
ReactDOM.render(
<React.StrickMode>
<Provider store = {store}>
<App />
</Provider>
</React.StrickMode>
document.getElementById('root')
);
//App.js 컴포넌트에서 사용하기
import './App.css';
import {useSelector} from 'react-redux'
function App(){
const 꺼내온거 = useSelector((state)=>state); //100
return(
<div classname = 'App'>
<p>님의 처참한 몸무게: {꺼내온거}</p>
</div>
);
}
export default App;
2. state관리하기 쉬움
- state사용하다가 버그가 생겼을 때, 일일이 찾을 필요가 없이
- redux를 사용하면 state를 저장해논데에 수정 방법(reducer)을 정의해 놓을 수 있음
- 다른 컴포넌트들은 수정 요청만 할 수 있음(dispatch함수 사용, 요청명 적기)
- 똑같은 기능을 제공하는 라이브러리: MobX, Overmind.js, Recoil
- 장점
- 버그가 일어났을 때 추적이 쉽다
- 큰 프로젝트를 진행할 때 필수
//index.js 세팅 + state보관 + state 수정방법 정의
import {Provider} from 'react-redux';
import {createStore} from 'redux';
const 체중 = 100; //state마음대로 보관 가능
//if문으로 수정방법 정의: reducer
function reducer(state = 체중, action){
if(action.type === '증가'){
state++; //증가요청하면 몸무게 +1
return state
} else if(action.type === '감소'){
state--; //감소요청하면 몸무게 -1
return state
} else{
return state
}
}
let store = createStore(reducer)
ReactDOM.render(
<React.StrickMode>
<Provider store = {store}>
<App />
</Provider>
</React.StrickMode>
document.getElementById('root')
);
//App.js 컴포넌트에서 사용하기
import './App.css';
import {useDispatch, useSelector} from 'react-redux'//dispatch사용
function App(){
const 꺼내온거 = useSelector((state)=>state); //100
const dispatch = useDispatch()
return(
<div classname = 'App'>
<p>님의 처참한 몸무게: {꺼내온거}</p>
<button onClick = {()=>{dispatch({type: '증가'})}}>더하기</button>//dispatch함수 쓰고, 요청명쓰기
</div>
);
}
export default App;
Redux의 작동 방식
'한' 개의 데이터(상태)를 저장하는 store가 있음. 컴포넌트는 절대 저장된 데이터를 직접 조작하지 않음.
1. store: 컴포넌트는 그 store를 구독하고, 필요한 데이터를 store->컴포넌트 받음
절대로 컴포넌트 -> store로 데이터가 흐르지 않음
2. reducer function: 변경되는 데이터는 Reducer Fn을 사용하여 데이터의 변형, 업데이트를 담당함
*Reducer Fn은 useReducer 훅과는 다름. 일반적인 개념으로 input을 받아 그 input을 변화하고 줄이는 역할을 함
입력을 변환해서 새로운 출력, 새로운 결과를 뱉어냄
3. action: 'trigger'를 함. 컴포넌트가 action을 발송(dispatch)함. action은 reducer작동해야되는 작업을 설명하는 단순한 '자바스크립트 객체'임.
- 리덕스는 그 액션을 reducer로 전달하고, 원하는 작업에 대한 설명을 읽음.
- 그 후 reducer가 그 작업을 수행함.
- 그 후 reducer가 새로운 상태를 뱉으면 그 상태가 store에서 기존의 상태를 대체함.
- 그 후 구독하는 컴포넌트가 알게되고, 컴포넌트는 UI를 업데이트함
Javascript에서의 Redux
state와 render의 관계
- Redux의 핵심은 store
- store를 은행에 비유해서 생각
- 은행에서 직접 돈을 못만지고 창구 직원(dispatch, subscribe, getState)를 통해야함
- 정보가 저장되는 곳
- store안에는 state라고 하는 실제 정보가 저장
- !절대로 state에 직접 조작하는게 불가능!
- store를 은행에 비유해서 생각
- store를 만들때 reducer라는 함수를 만들어 (인자로) 공급해주어야함
function reducer(oldState, action){
//...
}
var store = Redux.createStore(reducer);
- render : store안쪽에 있지 않음. UI를 만들어주는 역할을 하는 '내'가 짤 코드
function render(){
var state = store.getState();
//...
document.querySelector('#app').innerHTML =
<h1>WEB</h1>
...
}
store의 state값이 바뀔 때 마다 render함수를 불러오면 얼마나 좋을까? - subscribe
store.subscribe(render);
action과 reducer
- action은 객체
- type: create
- action이 -> dispatch에게 전달
- dispatch의 역할: 1. reducer를 호출해서 state값을 바꿈 2.subscribe를 이용하여 render함수를 호출하여 화면을 갱신
- 1. reducer를 호출해서 state의 값을 바꿈
- Reducer함수 호출시 2개의 값을 전달함: 1) 현재의 state값 2)action데이터
- 이때 reducer의 return 객체는 state의 새로운 값
- state를 입력값으로 받고, action을 참조해서 새로운 state값을 return해주는 'state 가공자'
- 1. reducer를 호출해서 state의 값을 바꿈
function reducer(state, action){
if(action.type === 'create'){
var newContents = oldState.contents.concat();
var newMaxId = oldState.maxId+1;
newContents.push({id: newMaxId, title: action.payload})
return Object.assign({}, state, {
contents: newContents,
maxId: newMaxId,
mode: 'read',
selectId: newMaxId
})
}
}
- dispatch역할
- 2. subscribe를 이용하여 render함수를 호출하여 화면을 갱신
- 새로운 state값이 return되면 render가 다시 호출되야함
- dispatch가 subscribe에 등록되어 있는 구독자들을 다 호출해줌 → render 호출 → 이후 일 반복 (render→getState→state→getState→render→UI그려줌)
- 2. subscribe를 이용하여 render함수를 호출하여 화면을 갱신
요약
- 핵심은 state
- state를 기반으로 화면을 그려준다
- store에 있는 state에 직접 접근하는게 금지되어 있기 때문에 중간에 getState를 통해 값을 가져온다
- dispatch를 통해 값을 변경시킨다
- subscribe를 이용해서 값이 변경됬을때 구동될 함수들을 등록해준다
- reducer를 통해서 state값을 변경한다
실습 1
Without Redux
- 부품(component)끼리 강력하게 coupling이 되어 있음. 서로 의존하고 있음.
<html>
<body>
<style>
.container {
border: 5px solid black;
padding: 10px;
}
</style>
<div id="red"></div>
<div id="green"></div>
<script>
function red() {
document.querySelector("#red").innerHTML = `
<div class="container" id="component_red">
<h1>red</h1>
<input type="button" value="fire" onclick="
document.querySelector('#component_red').style.backgroundColor='red';
document.querySelector('#component_green').style.backgroundColor='red';
">
</div>
`;
}
red();
function green() {
document.querySelector("#green").innerHTML = `
<div class="container" id="component_green">
<h1>green</h1>
<input type="button" value="fire" onclick="
document.querySelector('#component_red').style.backgroundColor='green';
document.querySelector('#component_green').style.backgroundColor='green';
">
</div>
`;
}
green();
</script>
</body>
</html>
With Redux
- 중앙 집중적 관리
- 부품(component)끼리 완전히 독립적(decoupling)
1. store생성
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
</head>
<body>
<style>
.container {
border: 5px solid black;
padding: 10px;
}
</style>
<div id="red"></div>
<div id="green"></div>
<script>
//(3)reducer를 통해 state값을 만들어 줄 때 reducer의 기존 state값이 undefined라면 원하는 초기값을 설정해주면 됨
function reducer(state, action) {
if (state === undefined) {
return { color: "yellow" };
}
}
var store = Redux.createStore(reducer);//(1)store를 만들면 내부적으로 state가 생김 (4)초기값이 지정됨
console.log(store.getState());//{ color: "yellow" }
function red() {
var state = store.getState();//(2)state값을 가져오려면 getState를 써야함 (5)state값을 가져옴
//(6)초기값을 지정할 수 있게됨
document.querySelector("#red").innerHTML = `
<div class="container" id="component_red" style="background-color:${state.color}">
<h1>red</h1>
<input type="button" value="fire" onclick="
document.querySelector('#component_red').style.backgroundColor='red';
document.querySelector('#component_green').style.backgroundColor='red';
">
</div>
`;
}
red();
</script>
</body>
</html>
2. reducer와 action을 이용해서 새로운 state값 만들기
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
</head>
<body>
<style>
.container {
border: 5px solid black;
padding: 10px;
}
</style>
<div id="red"></div>
<div id="green"></div>
<script>
//(2)dispatch는 reducer 함수를 호출함. 이때 oldState값과, 전달된 action의 값을 인자로줌. return값은 새로운 state값
//최초 한번은 무조건 실행, button이 트리거되었을 때 state값이 교체됨
function reducer(state, action) {
// console.log("state:", state);
// console.log("action:", action);
// console.log("=========");
if (state === undefined) {
return { color: "yellow" };
}
var newState;
if (action.type === "CHANGE_COLOR") {
//항상 oldState'복제'해서 새로운 state를 반환하게함
newState = Object.assign({}, state, { color: "red" });
}
return newState;
}
var store = Redux.createStore(reducer);
// console.log("getState:", store.getState());
function red() {
var state = store.getState();
//(1)store에 dispatch함수 호출, action(객체)를 줌. 이때 type은 반드시 넣기
document.querySelector("#red").innerHTML = `
<div class="container" id="component_red" style="background-color:${state.color}">
<h1>red</h1>
<input type="button" value="fire" onclick="
store.dispatch({type: 'CHANGE_COLOR', color: 'red'});
">
</div>
`;
}
red();
</script>
</body>
</html>
- reducer의 역할: store의 state값을 변경해줌
- action을 만들어 dispatch에게 제출하면, dispatch가 reducer를 호출
- 그때 oldState, action을 함께 넘김
- reducer함수가 그 둘을 분석해서 새로운 state값을 반환
- !state값을 받아서 변경하지말고, state값을 복제하고 그 복제본을 받아서 변경해라! (생활코딩 | immutabililty)
- 객체를 복제할때 Object.assign({}, )을 사용
- 첫번째 인자: 빈 객체, 두번째 인자: 빈 객체에 복제할 속성 =>새로운 객체가 복사됨
- Object.assign()의 return 값은 첫번째 인자
3. state의 변화에 따라서 UI에 반영하기
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
</head>
<body>
<style>
.container {
border: 5px solid black;
padding: 10px;
}
</style>
<div id="red"></div>
<div id="green"></div>
<script>
function reducer(state, action) {
// console.log("state:", state);
// console.log("action:", action);
// console.log("=========");
if (state === undefined) {
return { color: "yellow" };
}
var newState;
if (action.type === "CHANGE_COLOR") {
newState = Object.assign({}, state, { color: action.color }); //(2)하드코딩이 아닌 action.color로 변경
}
return newState;
}
var store = Redux.createStore(reducer);
function red() {
var state = store.getState();
document.querySelector("#red").innerHTML = `
<div class="container" id="component_red" style="background-color:${state.color}">
<h1>red</h1>
<input type="button" value="fire" onclick="
store.dispatch({type: 'CHANGE_COLOR', color: 'red'});
">
</div>
`;
}
store.subscribe(red); //(1)state값이 바뀔때마다 red함수가 호출됨
red();
function green() {
var state = store.getState();
document.querySelector("#green").innerHTML = `
<div class="container" id="component_red" style="background-color:${state.color}">
<h1>green</h1>
<input type="button" value="fire" onclick="
store.dispatch({type: 'CHANGE_COLOR', color: 'green'});
">
</div>
`;
}
store.subscribe(green);
green();
</script>
</body>
</html>
- state값이 바뀌면 render를 호출해서 state값을 가져와서 화면에 그리기
- subscribe에 render를 등록해 놓으면됨. 그럼 dispatch가 state값을 바꿈
실습 2
CRUD가 있는 앱 만들기
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
</head>
<body>
<div id="subject"></div>
<div id="toc"></div>
<div id="control"></div>
<div id="content"></div>
<script>
function subject() {
document.querySelector("#subject").innerHTML = `
<header>
<h1>WEB</h1>
Hello, WEB!
</header>
`;
}
function TOC() {
//(5) store에 있는 변수값을 getState를 통해 가져오기
var state = store.getState();
// console.log(state);
var i = 0;
var liTags = "";
//(6) state변화주기 위해서 action 정의하기 (7)store.dispatch에 action을 줌 =>store가 reducer를 호출함
while (i < state.contents.length) {
liTags =
liTags +
`
<li>
<a onclick="
event.preventDefault();
var action = {type:'SELECT', id:${state.contents[i].id}}
store.dispatch(action);
" href="${state.contents[i].id}">${state.contents[i].title}</a>
</li>
`;
i = i + 1;
}
document.querySelector("#toc").innerHTML = `
<nav>
<ol>${liTags}</ol>
</nav>
`;
}
function control() {
document.querySelector("#control").innerHTML = `
<ul>
<li><a onclick="
event.preventDefault();
store.dispatch({
type:'CHANGE_MODE',
mode:'create'
})
"
href="/create">create</a></li>
<li><input onclick="
store.dispatch({type: 'DELETE'})
"
type="button" value="delete" /></li>
</ul>
`;
}
function article() {
//(5),(6),(7)
var state = store.getState();
if (state.mode === "create") {
document.querySelector("#content").innerHTML = `
<article>
<form onsubmit="
event.preventDefault();
var _title =this.title.value;
var _desc =this.desc.value;
store.dispatch({
type:'CREATE',
title: _title,
desc: _desc
})
">
<p>
<input type="text" name="title" placeholder="title">
</p>
<p>
<textarea name="desc" placeholder="description"></textarea>
</p>
<p>
<input type="submit">
</p>
</form>
</article>`;
} else if (state.mode === "read") {
var i = 0;
var aTitle, aDesc;
while (i < state.contents.length) {
if (state.contents[i].id === state.selected_id) {
aTitle = state.contents[i].title;
aDesc = state.contents[i].desc;
break;
}
i = i + 1;
}
document.querySelector("#content").innerHTML = `
<article>
<h2>${aTitle}</h2>
${aDesc}
</article>`;
} else if (state.mode === "welcome") {
document.querySelector("#content").innerHTML = `
<article>
<h2>Welcome</h2>
Hellow, Redux!
</article>`;
}
}
//(2)꼭 reducer를 줘야한다, 2-1.이전 state값, 호출 이후 action값 입력
function reducer(state, action) {
// console.log(state, action);
//(3)초기값 설정
if (state === undefined) {
return {
max_id: 2,
mode: "welcome",
selected_id: 1,
contents: [
{ id: 1, title: "HTML", desc: "HTML is ..." },
{ id: 2, title: "CSS", desc: "CSS is ..." },
],
};
}
//(8) 정의된 action일 때, 기존 state복제하고
var newState;
if (action.type === "SELECT") {
newState = Object.assign({}, state, {
selected_id: action.id,
mode: "read",
});
} else if (action.type === "CREATE") {
var newMaxId = state.max_id + 1;
//배열일때는 concat사용하여 복제
var newContents = state.contents.concat();
newContents.push({
id: null,
title: action.title,
desc: action.desc,
});
//기존의 state복사
newState = Object.assign({}, state, {
max_id: newMaxId,
contents: newContents,
mode: "read",
});
} else if (action.type === "DELETE") {
var newContents = [];
var i = 0;
while (i < state.contents.length) {
if (state.selected_id !== state.contents[i].id) {
newContents.push(state.contents[i]);
}
i = i + 1;
}
newState = Object.assign({}, state, {
contents: newContents,
mode: "welcome",
});
} else if (action.type === "CHANGE_MODE") {
newState = Object.assign({}, state, {
mode: action.mode,
});
}
// console.log(action, state, newState);
return newState; //2-2. return값은 새로운 state
}
//(1)store를 만든다 (4)reducer를 createStore의 입력값으로 준 후, 변수화
var store = Redux.createStore(reducer);
//(9) article이 state값이 바뀔때마다 자동으로 article함수가 불러지도록
store.subscribe(article);
store.subscribe(TOC);
subject();
TOC();
control();
article();
</script>
</body>
</html>
시간여행 방법
1. Redux DevTools 활용
chrome web store | Redux DevTools
README.md | Redux DevTools Extension
const store = createStore(
reducer, /* preloadedState, */
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
2. reducer 함수에서 console.log를 찍어보기
function reducer(state, action) {
if (state === undefined) {
return { color: "yellow" };
}
var newState;
if (action.type === "CHANGE_COLOR") {
newState = Object.assign({}, state, { color: action.color });
}
console.log("action.type:", action.type);
console.log("action:", action);
console.log("state:", state);
console.log("newState:", newState)
console.log("==========");;
return newState;
}
Node.js에서 Redux
1. store생성: createStore()
2. reducer Fn생성: 2개의 매개변수를 받음(old state + dispatched action), new state object를 반환함. 하지만 redux가 처음 이 코드를 실행시켰을 때를 위해 기본 값을 정해놓아야함(그렇지 않으면 state는 undefined 상태)
store에 어떤 reducer함수를 연결해야되는지 넣어줌
*Reducer 함수는 순수함수. same input leads to same output. 중간에 side effect는 없어야함
3. 그 store를 구독할 누군가가 필요. 구독은 함수로 매개변수 받지 않고, 저장소에서 getState()로 상태를 불러옴. redux에 상태가 변경할 때마다 구독 함수를 실행하라고 말해줘야함. store.subscribe(counterSubscriber);
4. 발송할 Action(type속성이 있는 객체) 필요. 리덕스는 reducer 내부에서 '다른 일'을 하는게 목표.
5. reducer Fn내에서 각 action의 상황에 따라 변화하게 될 사항을 if문으로 정의함
//node.js
const redux=require('redux');
//2.reducer함수 정의-> 5번 후에 액션을 받고 그 후 어떻게 변경될지 if문을 통해 정의
const counterReducer = (state = {counter : 0}, action)=>{
if(action.type === 'increament'){
return{
counter: state.counter+1
};
}
if(action.type === 'decreament'){
return{
counter: state.counter-1
};
}
return state;
};
//1.store생성
const store=redux.createStore(counterReducer);
//3.구독 함수
const counterSubscriber = ()=>{
const latestState = store.getState()
console.log(latestState)
};
//4.redux에 상태가 변경할 때마다 구독 함수를 실행하라고 말해줘야함(실행은 안하고 point만함, 실행은 redux가 함)
store.subscribe(counterSubscriber)
//5.액션을 만들고 발송
store.dispatch({type: 'increment'});
store.dispatch({type: 'decrement'})
React에서 Redux
Store
1. store생성: 일반적으로 새로운 폴더를 생성함, reducer함수 만들기
*절대 기존의 state를 변형해서는 안됨. 대신 새로운 state객체를 반환하여 항상 재정의함
//store-index.js
import {createStore} from 'redux';
const initialState = {counter:0, showConter: true};
const counterReducer = (state=initialState, action) => {
if(action.type === 'increment'){
//state.counter++ 이렇게 기존의 state를 변경하는거 절대 불가
return{
counter: state.counter + 1,
showConter: state.showCounter
}
}
if(action.type === 'increase'){
return{
counter: state.counter + action.amount, //payload추가
showConter: state.showCounter
}
}
if(action.type === 'decrement'){
return{
counter: state.counter - 1
showConter: state.showCounter
}
if(action.type === 'toogle'){
return{
showCounter: !state.showCounter,
counter: state.counter
}
}
}
return state;
}
const store = createStore(counterReducer);
export default store;
2. store제공: 가장 최상위에서 파일에서(index.js) provider로 app을 감싸기. 이제 컴포넌트가 store를 사용할 수 있음
//index.js
import {Provider} from 'react-redux'
import store from './store/index'
<Provider store={store}><App/></Provider>
Component
3. 컴포넌트에서 useSelector로 컴포넌트에서 가져올 수 있게함. 그 후 component에서 useSelector훅을 이용하여 사용
4. useDispatch로 액션을 dispatch함
//Counter.js
import {useSelector, useDispatch} from 'react-redux';
const Counter =()=>{
const dispatch= useDispatch();
const counter = useSelector(state => state.counter);
const show = useSelector(state => state.showCounter);
const incrementHandler=()=>{
dispatch({type: 'increment'})
}
const increaseHandler=()=>{
dispatch({type: 'increment', amount: 5 }) //페이로드 추가
}
const decrementHandler=()=>{
dispatch({type: 'decrement'})
}
const toogleCounterHandler=()=>{
dispatch({type: 'toogle'})
}
return {
{show && <div>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
}
}
Redux에서 문제점
1. 식별자(type의 value를)를 잘못 치기가 쉬움
2. reducer함수의 크기가 엄청 커짐
3. 항상 모든 state의 snapshot을 반환해야하기 때문에 번거로움
Redux toolkit
툴킷을 사용하면 위 문제를 해결할 수 있음
툴킷을 설치하면 Redux는 삭제(툴킷에 포함되어 있음)
Store
1. store생성, 객체를 인자로 생성하는 createSlice를 사용하면 조각으로 사용할 수 있음. 관련되어 있지 않은 state는 여러 slice를 생성해서 유지관리가 편함. reducer함수에서 if문을 적지 않아도됨. 변경사항도 바로 적을 수 있음
2. configureStore로 저장소에 연결
3. action 식별자 만들기 counterSlice.actions
알아서 액션 객체가 만들어짐 (이미 type 속성을 가지고 있음)
//store-index.js
import {createStore} from 'redux';
import {createSlice, configureStore} from '@redux/js/toolkit';
const initialState = {counter:0, showConter: true};
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.payload;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
},
}
})
const store = configureStore({
reducer: counterSlice.reducer
});
//alternative
const store = configureStore({
reducer: { counter: counterSlice.reducer }
})
export const counterActions = counterSlice.actions;
export default store;
Component
4. 컴포넌트에서 action creators를 import해서 사용. 액션 객체를 바로 사용할 수 있음
//Counter.js
import {useSelector, useDispatch} from 'react-redux';
import {counterActions} from '../store/index'
const Counter =()=>{
const dispatch= useDispatch();
const counter = useSelector(state => state.counter);
const show = useSelector(state => state.showCounter);
const incrementHandler=()=>{
dispatch(counterActions.increment())
}
const increaseHandler=()=>{
dispatch(ounterActions.increase(10))//{type: SOME_UNIQUE_IDENTIFIER, payload: 10}
}
const decrementHandler=()=>{
dispatch(counterActions.decrement())
}
const toggleCounterHandler=()=>{
dispatch(ounterActions.toggleCounter())
}
return {
{show && <div>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
}
}
Redux Saga
- reducer는 순수 함수이기 때문에 데이터 fetching을 위한 비동기 처리 같은 부수적인 효과(side effects) 처리를 할 수 없음
- redux-saga는 부수적인 효과를 만들기 위한 라이브러리
- redux의 미들웨어
- redux saga가 나오기 전에는 이를 처리하기 위해 redux-thunk를 사용
출처
코딩애플 | React 입문자들이 알아야할 Redux 쉽게설명 (8분컷)
Udemy | React 완벽 가이드
'Javascript' 카테고리의 다른 글
함수형 프로그래밍 (0) | 2023.01.03 |
---|---|
ISO 8601 국제표준시간(2016-10-27T17:13:40Z) 날짜만 나오게 자르기 (0) | 2022.12.19 |
class (2) | 2022.11.28 |
생성자 함수와 new 연산자 (2) | 2022.11.26 |
This (0) | 2022.11.26 |