Provide / Inject
일반적으로 데이터를 부모에서 자식 컴포넌트로 전달할 때 props를 사용한다.
만일 조상 요소에서 후손 요소로 데이터를 전달할 때 props로 일일이 전달한다면 굉장히 복잡해질 것이다.
이럴 때 사용할 수 있는 것이 provide
와 inject
이다.
부모 컴포넌트는 데이터 제공을 위해 provide(제공)옵션을 사용하며, 자식 요소는 데이터 사용을 위해 inject(주입) 옵션을 사용한다.
<!-- App.vue -->
<script>
import Parent from '~/components/Parent';
export default {
components:{
Parent,
},
provide() {
return {
msg: this.msg, // provide로 데이터를 후손 요소로 보냄
};
},
data() {
return {
msg: "App Vue!",
};
},
};
</script>
<!-- Child.vue -->
<template>
<h1>Child {{ msg }}</h1>
</template>
<script>
export default {
inject: ["msg"], // inject로 조상 요소에서 보내준 데이터를 받음
};
</script>
하지만 provide
와 inject
를 사용해서 전달된 데이터는 반응형 데이터가 아니다.
반응형 작업
상위 컴포넌트의 변경사항에 반응하게 하려면 computed
함수를 사용한다.
<!-- App.vue -->
<script>
import Parent from '~/components/Parent';
import { computed } from "vue";
export default {
components:{
Parent,
},
provide() {
return {
msg: computed(() => this.msg),
};
},
data() {
return {
msg: "App Vue!",
};
},
};
</script>
<!-- Child.vue -->
<template>
<h1>Child {{ msg.value }}</h1>
</template>
computed 함수를 사용하게 되면 계산된 데이터 객체가 반환이 된다.
따라서 계산된 데이터의 값을 꺼내기 위해서는 .value
를 붙여야 한다.
provide와 inject를 사용하면 코드의 추적이 어려워지기 때문에 일반 애플리케이션에서 사용되는 것이 권장되지는 않는다.
Vuex(Store)
여러 컴포넌트에서 동시에 사용되는 데이터는 별도의 파일에 저장해서 전역으로 사용할 수 있다.
관리하고 사용할 데이터를 state, 상태라고 부른다.
// store/index.js
import { reactive } from "vue"; // 반응형 데이터를 제공해주는 vue 내장 함수
export const state = reactive({
msg: "Hello vue",
count: 1,
});
export const getters = {
// computed (계산된 데이터) 로직
reversedMsg() {
return state.msg.split("").reverse().join("");
},
};
export const mutations = {
// 데이터를 변경하는 로직
increaseCount() {
state.count += 1;
},
decreaseCount() {
state.count -= 1;
},
updateMsg(newMsg) {
state.msg = newMsg;
},
};
export const actions = {
// 기타 로직
async fetchTodo() {
const fetchResult = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
).then((res) => res.json());
mutations.updateMsg(fetchResult.title);
},
};
이렇게 별도의 파일로 관리하게 되면 다양한 컴포넌트에서 데이터를 사용할 수 있고 관리가 용이하다.
컴포넌트에서는 아래와 같이 store의 데이터를 사용할 수 있다.
<template>
<h1>Hello.vue</h1>
<div>{{ reversedMsg }}</div>
<div @click="increaseCount">
{{ count }}
</div>
<button @click="fetchTodo">
Get Todo
</button>
</template>
<script>
import { state, getters, mutations, actions } from '~/store';
export default {
data() {
return state;
},
computed:{
reversedMsg: getters.reversedMsg
},
methods:{
increaseCount: mutations.increaseCount,
fetchTodo: actions.fetchTodo
},
};
</script>
이러한 데이터 관리 로직을 조금 더 쉽게 해줄 수 있게 도와주는 라이브러리가 바로 Vuex이다.
Vuex는 상태 관리 패턴 라이브러리이다.
Vue 어플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소(store) 역할을 하게 된다.
Vuex는 다음 명령어로 설치한다.
npm i vuex@next
// main.js
import {createApp} from 'vue'
import App from '~/App'
import store from '~/store'
const app = createApp(App)
app.use(store) // vuex는 일종의 플러그인
app.mount('#app')
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state() {
return {
msg: 'Hello Vue?!',
count: 1
}
},
getters: {
reversedMsg(state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
increaseCount(state) {
state.count += 1
},
updateMsg(state, newMsg) {
state.msg = newMsg
}
},
actions: {
// context => state, getters, commit, dispatch
async fetchTodo(context) {
const todo = await fetch('https://jsonplaceholder.typicode.com/todos/1').then(res => res.json())
console.log(todo)
// commit(mutation 이름, 갱신할 데이터)
context.commit('updateMsg', todo.title)
}
}
})
state (상태)
store에서 다루는 데이터이다.
데이터는 함수로 만들어서 반환해야 참조 관계의 문제가 발생하지 않는다.
getters (계산된 데이터)
상태에 대한 계산된 결과를 얻어내고 싶을 때 사용한다.
하나의 상태에 의존해서 데이터를 만들어낸다.
첫 번째 인수로 해당 state를 참조할 수 있는 매개변수가 제공된다.
mutations (상태 변경)
데이터, 즉 상태를 변이시킬 때 mutaions의 도움을 받아서 변환한다. (상태의 변화를 추적하는 곳)
첫 번째 인수로 해당 state를 참조할 수 있는 매개변수가 제공된다.
actions (기타 로직)
store에서 가지고 있는 데이터를 활용해서 처리할 수 있는 대부분의 로직이 들어가는 부분이다.
첫 번째 인수로 다른 옵션들에 접근이 가능한 context
를 제공한다.
context에는 상태를 참조하는 state
, 계산된 상태를 참조하는 getters
, mutation을 실행할 수 있는 commit
, 다른 action을 실행할 수 있는 dispatch
가 있다.
mutaions
은 동기적으로 동작하고, actions
는 비동기적으로 동작한다.
모듈화
index.js라는 하나의 파일에서 프로젝트에서 사용하는 모든 데이터를 다루면 파일의 크기가 너무 커지고 관리하기가 어렵다.
이럴 때 사용할 수 있는 것이 모듈이라는 개념이다.
store에서 모듈을 사용하기 위해서는 모듈의 옵션으로 namespaced
의 값을 true
로 줘야 한다.
// store/message.js
export default {
namespaced: true, // 이름 범위로 데이터들을 사용할 수 있는 옵션
state() {
return {
message: 'Hello Store Module~~',
}
},
getters: {
reversedMessage(state) {
return state.message.split('').reverse().join('')
}
},
mutations: {
updateMessage(state, newMessage) {
state.message = newMessage
}
},
actions: {
async fetchTodo({commit}) {
const todo = await fetch('https://jsonplaceholder.typicode.com/todos/1').then(res => res.json())
commit('updateMessage', todo.title)
}
}
}
그리고 index.js에서 modules
옵션을 추가하고 모듈의 이름을 작성한다.
import { createStore } from 'vuex'
import message from './message'
import count from './count'
export default createStore({
state() {
return {
msg: 'Hello Vue?!',
}
},
getters: {},
mutations: {},
actions: {},
modules: {
message,
count
}
})
모듈화한 store의 state를 다루기 위해서는 아래와 같은 방식을 사용한다.
state
와 getters
는 computed에 등록한다.
- state
this.$store.state.NameSpace.State
- getters
this.$store.getters['NameSpace/Getter']
mutations
와 actions
는 methods에 등록한다.
- mutations
this.$store.commit('NameSpace/Mutation')
- actions
this.$store.dispatch('NameSpace/Action')
<template>
<h1>Hello.vue</h1>
<div>{{ count }}</div>
<button @click="increaseCount">
+
</button>
<button @click="decreaseCount">
-
</button>
<div>{{ message }} / {{ reversedMessage }}</div>
<button @click="fetchTodo">
Fetch Todo!
</button>
</template>
<script>
export default {
computed: {
count() {
return this.$store.state.count.count
},
message() {
return this.$store.state.message.message
},
reversedMessage() {
return this.$store.getters['message/reversedMessage']
}
},
methods: {
increaseCount() {
this.$store.commit('count/increaseCount')
},
decreaseCount() {
this.$store.commit('count/decreaseCount')
},
fetchTodo() {
this.$store.dispatch('message/fetchTodo')
}
}
}
</script>
<style scoped lang="scss">
$color: red;
h1 {
color: $color;
}
</style>
매핑
위 코드를 조금 더 간결하게 작성하기 위해 매핑 기능을 활용할 수 있다.
vuex의 내장 함수 mapState
, mapGetters
, mapMutations
, mapActions
를 활용하여 다음과 같이 작성한다.
<template>
<h1>Hello.vue</h1>
<div>{{ count }}</div>
<button @click="increaseCount">
+
</button>
<button @click="decreaseCount">
-
</button>
<div>{{ message }} / {{ reversedMessage }}</div>
<button @click="fetchTodo">
Fetch Todo!
</button>
<div>{{ msg }}</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
...mapState(['msg']), // 전역 store의 경우 네임스페이스를 작성하지 않음
...mapState('count', ['count']),
...mapState('message', ['message']),
...mapGetters('message', ['reversedMessage'])
},
methods: {
...mapMutations('count', ['increaseCount', 'decreaseCount']),
...mapActions('message', ['fetchTodo'])
}
}
</script>
출처: 프로그래머스 프론트엔드 데브코스
[Day 37] Vue (5)
'데브코스' 카테고리의 다른 글
[Day 40] Netlify Serverless Functions, 환경변수 (0) | 2022.12.15 |
---|---|
[Day 38] Vue Router, Babel, PostCSS (0) | 2022.12.11 |
[Day 37-1] Slots, Refs, Plugin, Mixin, Teleport (0) | 2022.12.10 |
[Day 36-2] 컴포넌트 등록, props, 커스텀 이벤트 (0) | 2022.12.09 |
[Day 36-1] Node.js, npm, Parcel, Webpack (0) | 2022.12.07 |