[Vue] Vuex : 상태관리 라이브러리

삔아·2021년 11월 23일
1

Vue.js

목록 보기
1/2
post-thumbnail

💡 Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리 입니다.

Vuex 개요

props & emit

Vue에서 데이터 전달하는 방법 중 하나는 바로 props와 emit을 이용하는 방법입니다.


이를 이용한 방법은 연관관계가 뚜렷하기 때문에 데이터의 전달 흐름을 잘 파악하기가 쉬운 장점이 있는데요.


다만 컴포넌트의 깊이에 따라 가상 최상위 컴포넌트부터 가장 최하위 컴포넌트까지 props를 전달하기엔 비효율적인 단점이 있습니다.

provide & inject

또 다른 방법은 provide와 inject를 이용하는 방법 입니다.

이 방법은 직계 부모보다 더 상위의 부모로부터도 데이터 주입 받을 수 있습니다.
그러나 데이터 흐름을 직관적으로 알 수 없다는 단점을 가지고 있습니다.
또한, 일반 어플리케이션 코드에서 사용하지 않는 것을 권장하며 보통 UI라이브러리의 컴포넌트를 설계할 때 props로(혹은 문서에) 드러내고 싶지 않은 특수한 케이스의 데이터 흐름을 정의할때 사용하기도 합니다.

Vuex

위에서 살펴본 두가지의 장,단점을 모두 합친것이 바로 Vuex 입니다!

  1. 뷰의 컴포넌트 통신 방식인 props, event emit 때문에 중간에 거쳐야 할 컴포넌트가 많아 지거나
  2. 이를 피하기 위해 Event Bus를 사용하여 컴포넌트 간 데이터 흐름을 파악하기 어려운 것

이런 문제점을 해결하기 위해 모든 데이터 통신을 한 곳에서 중앙 집중식으로 관리합니다.

Vuex


Vuex는 state, mutations, actions, getters 4가지 형태로 관리가 됩니다.
서로간 간접적으로 영향을 받으며 단방향 데이터 흐름으로 볼 수 있어 데이터의 흐름을 파악하기가 쉽습니다.

  1. Vue 컴포넌트는 상태를 검색할 때 저장소의 상태가 변경되면 효율적으로 대응하고 업데이트합니다.

  2. 저장소의 상태를 직접 변경할 수 없습니다. 저장소의 상태를 변경하는 유일한 방법은 명시적인 커밋을 이용한 변이 입니다. 이렇게하면 모든 상태에 대한 추적이 가능한 기록이 남을 수 있으며 툴을 사용하여 앱을 더 잘 이해할 수 있습니다.
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

state 객체에 store.state 로 접근하여 store.commit 메소드로 상태 변경을 트리거 할 수 있습니다.

store.commit('increment')

console.log(store.state.count) // -> 1

다시 말해, store.state.count를 직접 변경하는 대신 mutations를 수행하는 이유는 상태 변화를 지켜볼 수 있을뿐 더러 시간 흐름에 따라 디버깅을 할 수 있는 도구를 제공하기 때문에 state로 직접 변경하는걸 권장하지 않습니다.

State

💡 원본 소스의 역할을 하는 State

말 그대로 원본 소스, 즉 Vue 컴포넌트에서 data로 볼 수 있는데요.
이 state를 통해 직접적으로 접근이 가능하더라도, 다음에 설명 드릴 getter를 통해 접근을 하도록 합니다.
앞서 말했듯이 직접적인 변경이 아닌 mutation을 통해서만 변경이 가능합니다. 이 mutation을 통해 state가 변경이 일어나면 반응적으로 View 또한 업데이트 됩니다.

Getters

💡 자주 처리해야 하는 state의 데이터를 캐시를 통해 코드의 반복을 줄여줍니다.

store의 state가 필요할 때에 state에 직접 접근하는 것이 아닌 getter를 이용합니다.

Getters는 첫 번째 전달인자로 상태를 받습니다.

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

속성 유형 접근

💡 gettersstore.getters 객체에 노출되고, 속성으로 값에 접근할 수 있습니다:

store.getters.doneTodos 
// -> [{ id: 1, text: '...', done: true }]

Getters는 두 번째 전달인자로 다른 getter도 받게됩니다.

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}

store.getters.doneTodosCount // -> 1

이제 모든 컴포넌트에서 쉽게 사용할 수 있습니다.

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}

메소드 유형 접근

함수를 반환하여 getter에 전달인자로 전달할 수도 있습니다.

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}`

`store.getters.getTodoById(2) 
// -> { id: 2, text: '...', done: false }

메서드를 통해 접근하는 getter는 호출 할 때마다 실행되며 결과가 캐시되지 않는다는 것을 유의해야 합니다

Mutations

💡 Vuex 저장소에서 실제로 상태를 변경하는 유일한 방법은 Mutations 하는 것입니다.

Mutations는 이벤트와 매우 유사합니다. 핸들러 함수는 실제 상태 수정을 하는 곳이며, 첫 번째 전달인자로 상태를받습니다.

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 상태 변이
      state.count++
    }
  }
})

mutations를 직접 호출 할 수는 없습니다. 이 옵션은 이벤트 등록과 비슷합니다. 타입이 increment인 mutations가 발생하면 이 핸들러를 호출합니다.

mutations 핸들러를 호출하려면 해당 타입과 함께 store.commit 을 호출해야합니다.

store.commit('increment')

페이로드를 가진 커밋

변이에 대해 payload 라고하는 store.commit에 추가 전달인자를 사용 할 수 있습니다.

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}

store.commit('increment', 10)

대부분의 경우 페이로드는 여러 필드를 포함할 수 있는 객체여야하며 기록 된 mutations는 더 이해하기 쉽습니다.

// ...
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
store.commit('increment', {
  amount: 10
})

객체 스타일 커밋

mutations 을 이용하는 또 다른 방법은 type 속성을 가진 객체를 직접 사용하는 것입니다.

store.commit({
  type: 'increment',
  amount: 10
})

객체 스타일 커밋을 사용할 때 전체 객체는 mutations 핸들러에 페이로드로 전달되므로 핸들러는 동일하게 유지됩니다.

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

Vue의 반응성 규칙을 따르는 Mutations

Vuex 저장소의 상태는 Vue에 의해 반응하므로, 상태를 변경하면 상태를 관찰하는 Vue 컴포넌트가 자동으로 업데이트됩니다.

Mutations는 무조건 동기적이어야 합니다.

Actions

Actions는 Mutations와 유사합니다.

다만 이 작업에는 임의의 비동기 작업이 포함될 수 있습니다.

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Actions 핸들러는 저장소 인스턴스의 같은 메소드들/프로퍼티 세트를 드러내는 컨텍스트 객체를 받습니다.

그래서 context.commit을 호출하여 변이를 커밋하거나 context.state와 context.getters를 통해 상태와 getters에 접근 할 수 있습니다.

실제로 (특히 commit를 여러 번 호출해야하는 경우)코드를 단순화하기 위해 ES2015 전달인자 분해 (opens new window)를 사용합니다.

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

Dispatch

💡 액션은 store.dispatch 메소드로 시작됩니다.
액션 내에서 비동기 작업을 수행 할 수 있습니다.

store.dispatch('increment')

[참고] Module

💡 저장소를 모듈 로 나눌 수 있습니다. 각 모듈은 자체 상태, 변이, 액션, 게터 및 심지어 중첩된 모듈을 포함 할 수 있습니다.

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA'의 상태
store.state.b // -> moduleB'의 상태

지역 상태 모듈

모듈의 변이와 getter 내부에서 첫 번째 전달인자는 모듈의 지역 상태 가됩니다.

const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increment (state) {
      // state는 지역 모듈 상태 입니다
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

유사하게 모듈 내부에서 context.state는 지역 상태를 노출시킬 것이고 루트 상태는 context.rootState로 노출 될 것입니다.

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

또한, 모듈 getters 내부, 루트 상태는 그들의 세 번째 전달인자로 노출됩니다.

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

🔎 정리하며

프로젝트의 규모가 클수록 Vuex는 필수가 되어지는데요.
store의 설계에 주의하며 중앙 집중식 저장소 라는 개념이란것을 잘 파악하면 나름대로 쉽게 접근 할 수 있는것 같습니다.

profile
Frontend 개발자 입니다, 피드백은 언제나 환영 입니다

0개의 댓글