react에서 매일 써왔던 state(상태) 이 개념을 알고는 있지만 정확하게 무엇이다 라고 정의하긴 어려웠다. 하지만 여기서 알아보자.
state는 data로 변환가능하다. 이때 data는 애플리케이션이 필요로 하는 데이터와 사용자가 화면에서 무엇을 보는지에 영향을 주는 데이터를 뜻한다.
즉, state는 reactive한 data라고 볼 수 있다. 이 state는 reactive 하게 변경될 수 있으며 변경함으로써 화면상의 무언가를 트리거하는 데이터이다. 하지만 또 다른 기능 app이 필요로 하는 데이터이기도 한데 이 데이터를 관리하는 것은 어려울 수 있다.
local state : 지역 상태는 하나의 컴포넌트 내부에서 관리되는 데이터, state(상태)이다.
global state : 여러 컴포넌트 혹은 앱 전체에 걸쳐 영향을 주는 데이터, state(상태)이다.
앱 전체 또는 전역 상태 관리는 어려울 수 있다.
다른 컴포넌트엔 필요하더라도 해당 컴포넌트 템플릿엔 필요도 없는 엄청나게 많은 로직이 포함된 거대한 컴포넌트가 만들어질지도 모른다.
예측할 수 없는 동작이 애플리케이션에 일어날 수도 있다.
어디서 상태가 변경되는지 즉시 눈에 띄게 나타나지 않거나 원하지 않는 방향으로 상태가 변경되어버릴지도 모르는다.
실수로 상태 업데이트가 발생하거나 누락될 수도 있으니 오류도 자주 발생한다.
상태 관리를 Vuex에 아웃소싱하여 엄청나게 큰 로직을 안은 컴포넌트도 생기지 않는다.
왜냐면 컴포넌트와 분리되어 별도로 위치할 거니까
예측이 가능하다.
Vuex에는 상태가 어디서 관리되어야 하고 어떻게 업데이트 및 공유되어야 하는지에 대한 명확한 규칙이 있기 때문이다.
지켜야 할 규칙이 명확히 정의된 데이터 플로우가 있으니까 오류도 덜 발생한다.
npm install --save vuex
const store = createStore({
state(){
return{
key : value,
counter : 0
}
}
})
app.use(store)
app.use(router)
...
createStore는 저장소를 구성한 객체를 취한다. 저장소 구축 시 가장 중요한 건 바로 state인데 state는 상태 객체를 반환하는 메서드이다. 컴포넌트의 data(){}
와 비슷하다.
state(){}
가 애플리케이션의 상태가 되는데 즉, 이 객체는 애플리케이션 전체와 관련된 데이터를 보유한다.
<template>
<base-container title="Vuex">
<h3>{{ $store.state.counter }}</h3>
<button @click="addOne">Add 1</button>
</base-container>
</template>
<script>
methods : {
addOne(){
this.$store.state.counter++
}
}
</script>
모든 컴포넌트에서 액세스할 수 있는 새 프로퍼티가 있는데 바로 Vuex 저장소를 가리키는 $store
프로퍼티이다.
또한 이는 저장소에서 관리하는 상태를 가리키는 state 프로퍼티를 가진다.
.state
는 물론 main.js의 state 객체를 말하는 것이다.어디서나 상태를 바꿀 수 있다. Vuex를 다르게 사용해야 하는 이유기도 하다.
앱 전반에 걸친 중앙 데이터 저장소인 상태 저장소가 있고 컴포넌트 내부에서 이 저장소와 통신한다.
대신 Vuex에는 내장된 개념인 변형(Mutations)이 있다.
const store = createStore({
state(){
return{
counter : 0
}
},
mutations:{
increament(state){
state.counter += 2;
}
}
})
app.use(store);
store를 만든 main.js에 state 이외에도 mutations를 만든다.
mutations는 객체를 받고 이 객체에 메서드를 정의한다. 이 메서드는 상태를 변경하는 로직이다. react의 dispatcher처럼
이 메서드는 자동으로 현재 상태를 인수로 받는다. 이는 Vuex가 보장한다.
Vuex는 이 메서드가 트리거 될 때 현재 상태를 주는데 현재 상태가 여기 들어가기 때문에 최신 상태가 보장된다.
로직을 바꾸어야 한다면 여기 mutations에서 바꾸면 된다.
이제는 상태를 바꿔야 하는 모든 곳에서 mutations을 트리거하면 된다.
<template>
<button @click="addOne">Add 1</button>
</template>
<script>
export default {
methods: {
addOne() {
this.$store.commit("increament");
}
}
}
</script>
Mutation을 트리거하기 위해 this.$store에 접근하고 커밋(Commit) 메서드가 있는데 Vuex에 내장된 메서드로 store에 있다.
commit은 수행하고자 하는 Mutation의 이름을 받는다. 이 경우, increment이고 이때 이름은 "문자열"로 제공되어야 한다.
const store = createStore({
state(){
return{
counter : 0
}
},
mutations:{
increase(state, payload){
state.counter += payload.value;
}
}
})
...
methods: {
addOne() {
this.$store.commit("increase", {value:10});
}
}
...
위 mutation의 payload를 사용하위해 commit에 두 번째 인수인 payload를 넣는다.
이 두 번째 인수는 나중에 muations에서 받게 된다.
여기 두 번째 인수가 commit()에 전달하는 두 번째 인수가 된다.
...
methods: {
addOne() {
// this.$store.commit("increase", {value:10});
this.$store.commit({
type: "increase",
value:10
})
}
}
...
컴포넌트 내부에서 state를 직접 편집해서는 안 된다. 직접 state를 읽어오는 것도 이상적이지 않을 수 있다.
즉, 다른 컴포넌트에 같은 종류의 데이터가 필요할 때 데이터의 형식을 바꾸거나 계산값을 다르게 바꿔야 한다면 다른 모든 장소에서 해당 코드를 수정해야 한다는 문제점이 있고 이를 타파하기위해 게터(Getters)라는 개념을 사용할 수 있다.
결국에 이 개념은 연산(computed) 프로퍼티와 같이 저장소에 직접 정의되어 우리가 원하는 컴포넌트 내부에서 사용할 수 있다.
즉, 변경되는 유동적인 state를 관리할 때 사용, 커스텀 컴포넌트에서는 state에 직접 접근하기보다 Getter로 접근해야한다.
const store = createStore({
state(){
...
},
mutations:{
...
},
getters:{
finalCounter(state, getter){
return state.counter*2
},
normalizedCounter(_, getters){
const finalCounter = getters.finalCounter;
if(finalCounter < 0){
return 0
}
if(finalCounter > 100){
return 100
}
return finalCounter
}
}
})
getter는 객체를 받는다. 이제 이 객체의 모든 getter는 객체이다.
computed 프로퍼티가 컴포넌트의 computed 옵션에 있는 메서드인 것처럼 여기에 finalCounter라는 getter를 가질 수 있다.
finalCounter 게터 메서드는 Vuex에 있는 모든 getter 메서드처럼 두 개의 인수를 받는다.
<template>
<h3>{{counter}}</h3>
</template>
<script>
export default {
computed:{
counter(){
return this.$store.getters.finalCounter
}
}
}
</script>
여기서 호출하거나 실행하는 것이 아니고 그저 가리키는데 마치 프로퍼티처럼 가리킨다.
Vuex가 getter 메서드를 실행할 것이고 현재 상태를 getter에 전달한다.
즉, vue 구성객체 중 computed prop와 굉장히 유사하다.