Vue.js Vuex

강정우·2023년 4월 5일
0

vue.js

목록 보기
30/72
post-thumbnail

State란?

  • react에서 매일 써왔던 state(상태) 이 개념을 알고는 있지만 정확하게 무엇이다 라고 정의하긴 어려웠다. 하지만 여기서 알아보자.

  • state는 data로 변환가능하다. 이때 data는 애플리케이션이 필요로 하는 데이터와 사용자가 화면에서 무엇을 보는지에 영향을 주는 데이터를 뜻한다.

  • 즉, state는 reactive한 data라고 볼 수 있다. 이 state는 reactive 하게 변경될 수 있으며 변경함으로써 화면상의 무언가를 트리거하는 데이터이다. 하지만 또 다른 기능 app이 필요로 하는 데이터이기도 한데 이 데이터를 관리하는 것은 어려울 수 있다.

app component state

  • local state : 지역 상태는 하나의 컴포넌트 내부에서 관리되는 데이터, state(상태)이다.

    • 해당 컴포넌트에만 영향을 주며 프로퍼티를 통해 간접적으로 자식 컴포넌트에도 영향을 준다.
    • 예를들어 입력 요소에 입력된 사용자 입력값이나, 어떠한 컨테이너를 표시하거나 숨기는 버튼이다.
  • global state : 여러 컴포넌트 혹은 앱 전체에 걸쳐 영향을 주는 데이터, state(상태)이다.

    • 예를들어 사용자의 로그인 여부를 알리는 사용자 인증 상태, 앱의 다른 부분에서 표시될 수도 있는 장바구니 항목이다.

Vuex란?

VueX가 왜 필요할까?

  • 앱 전체 또는 전역 상태 관리는 어려울 수 있다.

  • 다른 컴포넌트엔 필요하더라도 해당 컴포넌트 템플릿엔 필요도 없는 엄청나게 많은 로직이 포함된 거대한 컴포넌트가 만들어질지도 모른다.

  • 예측할 수 없는 동작이 애플리케이션에 일어날 수도 있다.

  • 어디서 상태가 변경되는지 즉시 눈에 띄게 나타나지 않거나 원하지 않는 방향으로 상태가 변경되어버릴지도 모르는다.

  • 실수로 상태 업데이트가 발생하거나 누락될 수도 있으니 오류도 자주 발생한다.

반대로 Vuex를 사용하면

  • 상태 관리를 Vuex에 아웃소싱하여 엄청나게 큰 로직을 안은 컴포넌트도 생기지 않는다.
    왜냐면 컴포넌트와 분리되어 별도로 위치할 거니까

  • 예측이 가능하다.
    Vuex에는 상태가 어디서 관리되어야 하고 어떻게 업데이트 및 공유되어야 하는지에 대한 명확한 규칙이 있기 때문이다.

  • 지켜야 할 규칙이 명확히 정의된 데이터 플로우가 있으니까 오류도 덜 발생한다.

Vue 설치 및 사용

npm install --save vuex

프로젝트내에 설치

main.js

const store = createStore({
    state(){
        return{
        	key : value,
            counter : 0
        }
    }
})

app.use(store)
app.use(router)
...
  • createStore는 저장소를 구성한 객체를 취한다. 저장소 구축 시 가장 중요한 건 바로 state인데 state는 상태 객체를 반환하는 메서드이다. 컴포넌트의 data(){}와 비슷하다.

  • state(){} 가 애플리케이션의 상태가 되는데 즉, 이 객체는 애플리케이션 전체와 관련된 데이터를 보유한다.

    • 이때 store는 app당 1개의 저장소를 갖는다.

커스텀컴포넌트.vue

<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 객체를 말하는 것이다.

Mutations

  • 왜 쓸까? => 위 addOne() 메서드처럼 각 state가 쓰인곳에 변형을 주기위해 methods에 메서드를 등록하여 하나하나 써주었다.
    그러다면 addOne()이 바뀐다면 이 수많은 methods의 addOne을 일일이 바꿔주어야한다는 것이다. 이를 최소화하기 위해 사용되는 개념이 바로 Mutations이다.

상태 변화 메커니즘에 대한 명확한 정의하기

  • 어디서나 상태를 바꿀 수 있다. Vuex를 다르게 사용해야 하는 이유기도 하다.

  • 앱 전반에 걸친 중앙 데이터 저장소인 상태 저장소가 있고 컴포넌트 내부에서 이 저장소와 통신한다.

    • 통신은 해야 하지만 직접적으로는 하지 않는다.
  • 대신 Vuex에는 내장된 개념인 변형(Mutations)이 있다.

    • Mutations는 명확하게 정의된 메서드로 상태를 업데이트하는 로직을 가지고 있다.
    • 이는 컴포넌트 내부에 있으며 직접 상태를 바꾸는 대신에 Mutations을 트리거한다.
    • 변형을 트리거함으로써 상태를 편집하고자 하는 모든 컴포넌트는 같은 방식으로 동작하게 된다.

사용법

main.js

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을 트리거하면 된다.

커스텀컴포넌트.vue

<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이고 이때 이름은 "문자열"로 제공되어야 한다.

payload

  • redux에서는 payload가 정확한 명칭이었지만 vuex에서는 편의상 부르는 말이다.

main.js

const store = createStore({
    state(){
        return{
            counter : 0
        }
    },
    mutations:{
        increase(state, payload){
            state.counter += payload.value;
        }
    }
})
  • payload의 타입은 무엇이든 상관다. 숫자일 수도, 문자열일 수도 객체일 수도 있다.
    • 이때는 물론 payload.value가 아닌 그냥 payload만 와도 된다.
    • 여기서는 value 프로퍼티가 있는 객체라고 가정하겠다.

커스텀컴포넌트.vue

...
    methods: {
        addOne() {
            this.$store.commit("increase", {value:10});
        }
    }
...
  • 위 mutation의 payload를 사용하위해 commit에 두 번째 인수인 payload를 넣는다.

  • 이 두 번째 인수는 나중에 muations에서 받게 된다.

  • 여기 두 번째 인수가 commit()에 전달하는 두 번째 인수가 된다.

    • 데이터 형식은 어떤 것이든 상관다 숫자든, 문자열이든 상관없지만 여기선 value 프로퍼티를 가진 객체여야 한다.
    • value가 담긴 객체를 받을 거라 예상하기 때문이다.

커스텀컴포넌트.vue(commit.ver)

...
    methods: {
        addOne() {
            // this.$store.commit("increase", {value:10});
            this.$store.commit({
                type: "increase",
                value:10
            })
        }
    }
...
  • commit을 호출하고 "객체"를 전달할 수 있다.
    • 이 객체에는 type 프로퍼티가 필요하고 여기에 커밋해야 할 mutation의 이름을 넣는다.
    • 추가로, 원하는 만큼 많은 프로퍼티를 넣을 수 있다.
    • 예를 들어 value 프로퍼티를 넣을 수 있다. 그러면 이 객체는 타입 프로퍼티는 제외하고 increase 변형의 payload로 제공된다
    • commit() 하는 다른 방식이다

getter

  • 컴포넌트 내부에서 state를 직접 편집해서는 안 된다. 직접 state를 읽어오는 것도 이상적이지 않을 수 있다.

    • why? : 내가 짠 state 로직이 각기 다른 컴포넌트에서 여러번 쓰였다면 해당 state 로직을 바꿀 때 일일이 또 다 바꿔야하기 때문
  • 즉, 다른 컴포넌트에 같은 종류의 데이터가 필요할 때 데이터의 형식을 바꾸거나 계산값을 다르게 바꿔야 한다면 다른 모든 장소에서 해당 코드를 수정해야 한다는 문제점이 있고 이를 타파하기위해 게터(Getters)라는 개념을 사용할 수 있다.

  • 결국에 이 개념은 연산(computed) 프로퍼티와 같이 저장소에 직접 정의되어 우리가 원하는 컴포넌트 내부에서 사용할 수 있다.

즉, 변경되는 유동적인 state를 관리할 때 사용, 커스텀 컴포넌트에서는 state에 직접 접근하기보다 Getter로 접근해야한다.

main.js

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 메서드처럼 두 개의 인수를 받는다.

    • state에서는 현재 상태를 가져와야 하고 두 번째 인수는 getters이다
    • 여기서 계산하려는 값이 다른 게터의 결과에 따라 달라지는 경우 getter(normalizedCounter)에 다른 getter(finalCounter)를 넣는 것이 유용할 수 있다.

커스텀컴포넌트.vue

<template>
  <h3>{{counter}}</h3>
</template>

<script>
export default {
    computed:{
        counter(){
            return this.$store.getters.finalCounter
        }
    }
}
</script>
  • 여기서 호출하거나 실행하는 것이 아니고 그저 가리키는데 마치 프로퍼티처럼 가리킨다.

  • Vuex가 getter 메서드를 실행할 것이고 현재 상태를 getter에 전달한다.

즉, vue 구성객체 중 computed prop와 굉장히 유사하다.

profile
智(지)! 德(덕)! 體(체)!

0개의 댓글