npm init -y
터미널 창에 입력을 하면 빨강색 네모 박스 부분이 생깁니다.
const redux = require("redux");
기존의 import 방식과 다르게 redux를 불러옵니다.
const counterReducer = (state , action) => {
return{
...
};
}
기본적인 redux의 구조이고
const store = redux.createStore(counterReducer);
redux를 저장할 저장소를 생성해야하고,
const counterSubscriber = () => {
// 구독
const latestState = store.getState(); //최신상태
console.log(latestState);
};
store.subscribe(counterSubscriber);
구독자도 생성해 줍니다.
store.dispatch({ type: "increment" }); //dispatch는 액션을 발송하는 메서드
action을 발송하는 dispatch 메서드를 통해서 객체를 생성해주면
counterReducer가 재실행되는 것을 볼 수 있습니다.
const counterReducer = (state = { counter: 0 }, action) => {
return {
counter: state.counter + 1, //기존 상태에 1을더한 것
};
};
기본적으로 counter의 기본 값을 정해줘야합니다. 여기서는 0 으로 state에 정해주었습니다.
ux-example> node redux-demo.js
{ counter: 1 }
{ counter: 2 }
터미널 창에서 counter가 두번 실행돼서 2가 되는 것을 볼 수 있습니다.
리듀서 내부에서 액션을 살펴보겠습니다. 보통 action은 state와 다른 실행을 하기위해서 작동합니다.
const counterReducer = (state = { counter: 0 }, action) => {
if (action.type === "increment") {
return {
counter: state.counter + 1, //기존 상태에 1을더한 것
};
}
return state;
};
내부를 바꿔서 if 조건문을 달고
store.dispatch({ type:"decrement"});
dispatch를 추가해줍니다.
if (action.type === "decrement") {
return {
counter: state.counter - 1, //기존 상태에 1을뺸 것
};
}
그리고 리듀서 내부에 또 다른 if 조건문을 추가해주면
{ counter: 1 }
{ counter: 0 }
counter의 결과값이 잘 나오는 것이 확인이 됩니다.
> npm install redux react-redux
리덕스는 자바스크립트 프로젝트에서 쓰는 것이기 떄문에 우리는 리액트에서 써야합니다. 그러기 위해서는 터미널에 pm install redux 뒤에 react-redux를 붙혀줘야 합니다!
store 폴더 만들겠습니다. index.js파일을 만들고 이제 여기에 스토어를 만들고, 리듀서를 만들겠습니다! 하지만 구독하지는 않겠습니다.
import { legacy_createStore as createStore } from "redux";
createStore를 사용하려면 이 구문이 현재 버전에서 필요합니다!
const counterReducer = (state = { counter: 0 }, action) => {
if (action.type === "increment") {
return {
counter: state.counter + 1,
};
}
if (action.type === "decrement") {
return {
counter: state.counter - 1,
};
}
return state;
};
const store = createStore(counterReducer);
저번 시간과 마찬가지로 카운트를 증가/감소 시키는 것을 만들었는데 다른 점이 있습니다
store.dispatch({ type: "increment" });
store.dispatch({ type: "decrement" });
저번 시간에는 내부에서 dispatch를 해서 store를 연결하였다면
이번 시간에는 내부에서 dispatch할 것이 아니라 리액트 앱과 리덕스 스토어를 연결하길 원합니다.
export default store;
를 통해서 App.js에 스토어와 연결할 수 있습니다.
store/index.js에서 store를 App.js에 연결시키기 위해서는 상위 파일인 index.js로 갑니다.
App component를 Provider로 감싸줍니다!
현재 App.js에 가면 Counter Component로 사용하는 것을 볼 수 있습니다.
import { useSelector } from "react-redux";
useSelector는 자동으로 상태의 일부를 선택하게 해준다
const counter = useSelector((state) => state.counter);
Counter함수 안에 넣고 리액트 리덕스에 의해 자동으로 실행된다.
<div className={classes.value}>{counter}</div>
{counter}로 value를 집어넣으면
기본 state value인 0이 뜨는 것을 볼 수 있습니다. 그러면 이 데이터를 어떻게 바꿀 수 있을까요?
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
먼저 button을 두 개 만들겠습니다.
import { useSelector, useDispatch } from "react-redux";
const dispatch = useDispatch();
useDispatch를 import 해줍니다.
const incrementHandler = () => {
dispatch({ type: "increment" });
};
const decrementHandler = () => {
dispatch({ type: "decrement" });
};
호출 함수에 dispatch로 원하는 type을 넣어주고
onClick={incrementHandler}
onClick={decrementHandler}
button 속성에 넣어줍니다.
클릭했을 떄 숫자가 잘 바뀌는 것을 볼 수 있습니다.
<button onClick={increaseHandler}>Increment by 5</button>
버튼을 새로 추가해 5씩 증가하는 버튼을 만들어 보겠습니다.
if (action.type === "increase") {
return {
counter: state.counter + action.amount,
};
}
index.js에서 state.counter + 5를 하여서 하드코딩을 할 수 있지만 이 것은 적합하지 못합니다.
const increaseHandler = () => {
dispatch({ type: "increase", amount: 5 });
};
action으로 amount라는 이름을 가진 것은 dispatch에서 같은 이름의 값으로 불러오는 것이 적합합니다.
버튼을 누르면 5씩 증가하는 것을 볼 수 있습니다.
ToggleButton을 클릭했을 떄 카운터가 사라졌다가 나타났으면 좋겠습니다. 그러기 위해선 useState를 이용해보겠습니다!
<button onClick={toggleCounterHandler}>Toggle Counter</button>
const toggleCounterHandler = () => {
dispatch({ type: "toggle" });
};
const initialState = { counter: 0, showCounter: true };
const counterReducer = (state = initialState, action) => {
if (action.type === "increment") {
return {
counter: state.counter + 1,
showCounter: state.showCounter, //기존의 showCounter를 벨류를 취할 것
};
}
...
if (action.type === "toggle") {
return {
showCounter: !state.showCounter,
counter: state.counter,
};
}
showCounter: state.showCounter를 각 type에 넣어줌으로써 기존의 showCounter의 value를 집어넣주는 문구를 넣어줍니다.
그리고 type이 toggle일 때 반대의 showCounter를 해주면 됩니다!
{show && <div className={classes.value}>{counter}</div>}
show가 true일 떄 counter가 보이게 동적으로 처리해줍니다.
toggle버튼 누른 후
counter가 사라지는 것을 볼 수 있습니다!
Redux로 작업을 할 때는 기존의 State를 절대 변경해서는 안됩니다! 이로 인해 버그인 예측 불가능한 동작이 발생할 수 있고 디버깅하는 것도 어려워 질 수도 있습니다.
if (action.type === "increment") {
state.counter++;
return {
counter: state.counter,
showCounter: state.showCounter,
};
}
이렇게 return 전에 state.counter++; 기존의 state를 변경시켜서 return을 하게되면 안된다는 말이다.
if (action.type === "increment") {
return {
counter: state.counter + 1,
showCounter: state.showCounter, //기존의 showCounter를 벨류를 ㅊ튀할 것
};
}
counter: state.counter + 1, 처럼 새로운 객체나 배열을 만들서
return을 해줘야한다!
더 쉽게 리덕스를 사용할 수 있는 방법이 있습니다! 바로 Redux toolkit라는 라이브러리가 있습니다!
npm install @reduxjs/toolkit
터미널에서 추가해줍니다!
그리고 package.json에서 redux를 제거해주세요!
import { createSlice } from '@reduxjs/toolkit';
createSlice({
name: "counter",
initialState, //initialState: initialState
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
tate.counter++;
},
increase(state, action) {
state.counter = state.counter + action.amount;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
},
},
});
기존에 사용할 수 없었던 state.counter++; 직접 변경하는 것이 가능합니다.
increase(state, action) {
state.counter = state.counter + action.amount;
},
increase는원하는 값만큼 변경시켜야하기 떄문에 action을 받습니다.
import { createSlice, configureStore } from "@reduxjs/toolkit";
configureStore 를 import해주고
const counterSlice = createSlice({
name: "counter",
initialState, //initialState: initialState
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter++;
},
increase(state, action) {
state.counter = state.counter + action.amount;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
},
},
});
이름을 붙혀 const counterSlice 전역변수로 설정해줍니다.
const store = configureStore({
reducer:counterSlice.reducer
});
configureStore의 메인 리듀서로 counterSlice를 할당해줍니다!
이제 action을 이용해 접근 해보겠습니다!
export const counterActions = counterSlice.actions
리듀서안에 메서드와 같은 이름을 붙이면 자동으로 액션 생성자 액션 객체를 만든다
이렇게하고 액션이 필요한 컴포넌트로 가면 됩니다.
import { counterActions } from "../store/index";
counterActions를 imnport 해주고
const incrementHandler = () => {
dispatch(counterActions.increment()); //전체 액션 객체가 자동으로 생성
};
const decrementHandler = () => {
dispatch(counterActions.decrement());
};
const increaseHandler = () => {
dispatch(counterActions.increase(5)); //{ type:IDENTIFIER payload: 5}
};
const toggleCounterHandler = () => {
dispatch(counterActions.toggleCounter());
};
dispatch()에 counterActions . 리덕스안에 메서드이름을 붙혀주고
특히, increaseHandler에서는 increase(5) 5매개변수를 넣고 이 매개변수 이름은 payload라는 변수명으로 고정되어있다.
다시 store/index.js로 돌아가서
increase(state, action) {
state.counter = state.counter + action.payload;
},
state.counter = state.counter + action.amount;에서 action.payload;로 바꿔야한다.
function App() {
return (
<Fragment>
<Header />
<Auth />
<Counter />
</Fragment>
);
}
const initialAuthState = {
isAuthenticated: false,
};
const authSlice = createSlice({
name: "authentication",
initialState: initialAuthState,
reducers: {
login(state) {
state.isAuthenticated = true;
},
logout(state) {
state.isAuthenticated = false;
},
},
});
로그인 인증에 대한 것을 확인해주기위해 새로운 메서드를 만듭니다.
const store = configureStore({
reducer: { counter: counterSlice.reducer, auth: authSlice.reducer },
});
그리고 reducer:는 map형태로도 받을 수 있다고 했습니다. authSlice도 추가가 가능하고 이때 변수명을 각각 counter, auth로 진행해줍니다!
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter.counter);
const show = useSelector((state) => state.counter.showCounter);
그리고 리듀서에서 reducer의 저장소에 이름을 counter 라는 key명으로 저장했기 때문에
state.counter.counter/ state.counter.showCounter
counter를 꼭 붙여줘야합니다!
로그인 했을 떄만 메뉴버튼과 로그아웃 버튼이 보이게 만들어보겠습니다!
import { useSelector } from "react-redux";
const isAuth = useSelector((state) => state.auth.isAuthenticated);
...
{!isAuth && <Auth />}
{isAuth && <UserProfile />}
import { useSelector, useDispatch } from "react-redux";
import { authActions } from "../store/index";
const dispatch = useDispatch();
const isAuth = useSelector((state) => state.auth.isAuthenticated);
...
{isAuth && (
<nav>
<ul>
<li>
<a href="/">My Products</a>
</li>
<li>
<a href="/">My Sales</a>
</li>
<li>
<button onClick={logoutHandler}>Logout</button>
</li>
</ul>
</nav>
)}
import { useDispatch } from "react-redux";
import { authActions } from "../store/index";
const dispatch = useDispatch();
const loginHandler = (e) => {
e.preventDefault();
dispatch(authActions.login());
};
...
<form onSubmit={loginHandler}>
const logoutHandler = () => {
dispatch(authActions.logout());
};
<button onClick={logoutHandler}>Logout</button>
로그인을 누르면 메뉴가 나타나는 것을 볼 수 있다.