💡 Redux는 모든 JavaScript에서 활용 가능한 상태관리 라이브러리이다.
Redux에는 총 4가지 속성이 있다.
Redux에는 3가지 규칙이있다.
리액트에서 Redux를 활용해 counter를 제작해보자
/* store/counter.js */
// toolkit 활용시 두 함수들을 import한다.
import { createAction, createReducer } from '@reduxjs/toolkit';
// 1. action의 타입을 정의한다.
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
// 2. action 생성 함수를 제작한다.
export const increase = () => ({ type: INCRESAE });
export const decrease = () => ({ type: DECREASE });
// toolkit을 활용한다면 아래와 같이 변환 가능
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
// 만약 1번 단계를 생략한다면?
export const increase = createAction('counter/INCREASE');
export const decresae = createAction('counter/DECREASE');
// 3. 초기 상태(state)를 설정한다.
// 이 과정은 toolkit도 동일하다.
const initialState = {
value: 0,
};
// 4. reducer를 제작한다.
const counter = (state, action) => {
switch(action.type) {
case INCREASE:
return {
...state,
value: state.value + 1
};
case DECREASE:
return {
...state,
value: state.value - 1
};
default:
return state;
}
};
// toolkit을 활용하면 아래와 같이 변환 가능
const counter = createReducer(initialState, {
[INCREASE]: (state) => ({ ...state, value: state.value + 1 }),
[DECREASE]: (state) => ({ ...state, value: state.value - 1 }),
// 최상위에서 actio의 타입을 지정해주지 않았다면 createAction에서 첫번째인자는
// string타입의 값을 받아 해당 액션의 타입의 객체를 리턴하기 때문에 함수.type으로도 가능하다.
[increase.type]: (state) => ({ ...state, value: state.value + 1 }),
[decrease.type]: (state) => ({ ...state, value: state.value - 1 }),
})
// toolkit에서 객체형 리듀서가 곧 deprecated될 예정으로 builder 콜백을 사용하는 것을 권장하고 있다.
const counter = createReducer(initialState, (builder) => {
builder
.addCase(INCREASE, (state) => ({ ...state, value: state.value + 1 })
.addCase(DECREASE, (state) => ({ ...state, value: state.value - 1 })
});
export default counter;
리듀서까지 완성이 됐다면 여러가지 리듀서들을 하나의 리듀서로 통합해주는 작업을 한다. (현재는 하나이지만 추후에도 추가될 리듀서들을 위해 미리 하나의 리듀서로 통합한다.)
/* store/index.js */
import { combineReducers } from 'redux'; // 1. combineReducers를 import
import counter from './counter';
const rootReducer = combineReducers({ counter }); // 3. 객체의 형태로 넣어주고
export default rootReducer; // 4. 합쳐진 리듀서들을 export 시킨다.
리듀서까지 통합되었으면 index.js
로 가서 store를 생성하고 React-Redux에서 제공하는 Provider
를 통해 App컴포넌트를 감싸준다.
/* index.js */
import ReactDOM from 'react-dom/client';
import { configureStore, createStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import rootReducer from './store/index';
// 기존 리덕스 라이브러리에서 제공하는 createStore를 활용해서 아래와 같이 store를 제작할 수 있으나
// 리덕스에서는 툴킷을 쓰는 것을 권장하고 있다.
const store = createStore(rootReducer);
// toolkit에서는 createStore를 대체하여 configureStore가 등장했다.
const store = configureStore({ reducer: rootReducer });
const root = ReactDOM.createRoot(
document.getElementById('root)
);
root.render(
// Provider를 활용해 store를 부여해주고
// App 컴포넌트(혹은 상황에 따라 Router컴포넌트)를 감싸주면 세팅은 완료된다.
<Provider store={store}>
<App />
</Provider>
);
추가적으로 액션생성함수들을 실행시키기 위해서는 꼭 dispatch
함수를 활용해서 리듀서를 통해 상태들이 바뀌도록 해줘야한다.
createStore와 configureStore의 큰 차이점은 createStore는 브라우저에서 리덕스가 작동되는 액션을 추적 할 수 없는데 추적을 하려면 redux-devtools-extension라이브러리를 설치해서 createStore에 추가를 해주어야 추적이 가능하다.
하지만 configureStore는 자체적으로 내장이 되어있어 별도의 라이브러리 설치 없이 추적이 가능한 장점이 있다.
또한 createStore를 활용할 땐 통합된 리듀서를 그냥 넣어주고 미들웨어를 추가하려면 applyMiddleware를 활용해서 각종 미들웨어를 넣어줘야 했지만 configureStore에서 미들웨어를 추가할 때는 객체안에 middleware 키값으로 사용하고자 하는 미들웨어들은 배열의 형태로 넣어주면 된다.
React에 Toolkit이 있다면 Vue에는 Vuex가 있다.
Vuex에도 마찬가지로 4가지 속성이 있다.
리액트와의 차이점 중 하나로 actions부분이 가장 크다고 생각한다.
마찬가지로 Vue에서 Vuex를 활용해 counter를 만들어보자.
/* store/store.js */
import { createStore } from 'vuex';
const store = createStore({
// 1. 상태를 저장
state() {
return {
value: 0,
}
},
// 2. 변이(mutations) 함수 생성
mutations: {
increase(state) {
// 함수의 첫번째 인자는 상태(state)를 의미하고
// 두번째 인자는 payload, 이 mutations함수가 들고오는 외부 요소를 의미한다.
state.value++;
},
decrease(state) {
state.value--;
},
},
// 3. 비동기 처리를 위한 액션(actions)함수 생성
actions: {
increaseAsync(context) {
setTimeout(() => {
// 주의사항: 모든 상태의 변화는 필히 mutations 함수로만 이루어져야 한다.
context.commit('increase');
}, 1000);
},
decreaseAsync(context) {
setTimeout(() => {
context.commit('decrease');
}, 1000);
},
});
export default store;
store제작이 완료되었다면 main.js로 이동해 해당 store를 사용한다고 명시해준다.
import { createApp } from 'vue';
import store from './store/store'; // 제작해둔 store를 import해서
import App from './App.vue';
const app = createApp(App);
app.use(store); // App컴포넌트에 주입하면 끝!
app.mount('#app');
store 적용까지 완료되었다면 사용만 하면 된다.
<!-- 1. 직접 store로 접근 -->
<template>
<div>count is: {{ $store.state.value }}</div>
</template>
<!-- 2. computed에 등록 -->
<template>
<div>count is: {{ value }}</div>
</template>
<script>
export default {
computed: {
value() {
return this.$store.state.value
}
}
}
</script>
<!-- 3. mapState를 활용한 접근 -->
<template>
<div>count is: {{ value }}</div>
</template>
<script>
export default {
computed: {
...mapState(["value"]),
},
}
</script>
상태(state)는 위에 있는 것처럼 접근하고 가져올 수 있으며 이 상태들을 변형 시키고 싶다면 mutations함수들을 활용해야 한다.
<!-- 1. 바로 변이를 시키는 방법 -->
<template>
<div>count is: {{ value }}</div>
<!-- mutations함수를 실행시키기 위해선 commit이라는 메서드를 활용한다 -->
<button @click="$store.commit('decrease')">-1</button>
<button @click="$store.commit('increase')">+1</button>
</template>
<script>
export default {
computed: {
...mapState(["value"]),
},
}
</script>
<!-- 2. mapMutaions를 활용하는 방법 -->
<template>
<div @click="$store.commit('increase')">
count is: {{ value }}
</div>
<button @click="increase">-1</button>
<button @click="decrease">+1</button>
</template>
<script>
export default {
computed: {
...mapState(["value"]),
},
methods: {
...mapMutations(['increase', 'decrease']),
}
}
</script>
비동기 처리를 위한 actions함수를 실행시키기 위한 방법
<!-- 1. 바로 변이를 시키는 방법 -->
<template>
<div>count is: {{ value }}</div>
<!-- actions함수를 실행시키기 위해선 dispatch라는 메서드를 활용한다 -->
<button @click="$store.dispatch('decreaseAsync')">1초뒤 -1</button>
<button @click="$store.dispatch('increaseAsync')">1초뒤 +1</button>
</template>
<script>
export default {
computed: {
...mapState(["value"]),
},
}
</script>
<!-- 2. mapActions를 활용하는 방법 -->
<template>
<div @click="$store.commit('increase')">
count is: {{ value }}
</div>
<button @click="increaseAsync">1초뒤 -1</button>
<button @click="decreaseAsync">1초뒤 +1</button>
</template>
<script>
export default {
computed: {
...mapState(["value"]),
},
methods: {
...mapActions(['increaseAsync', 'decreaseAsync']),
}
}
</script>