# 모듈

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/cqKK4psq" target="_blank" rel="noopener noreferrer">Scrimba에서이 수업을 해보십시오.</a></div>

단일 상태 트리를 사용하기 때문에 애플리케이션의 모든 상태가 하나의 큰 객체 안에 포함됩니다. 그러나 규모가 커짐에 따라 저장소는 매우 비대해질 수 있습니다.

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

``` js
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 내부에서 첫 번째 전달인자는 **모듈의 지역 상태** 가됩니다.

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

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

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

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

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

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

### 네임스페이스

기본적으로 모듈 내의 액션, 변이 및 getter는 여전히 **전역 네임 스페이스** 아래에 등록됩니다. 여러 모듈이 동일한 변이/액션 유형에 반응 할 수 있습니다.

만약 모듈이 독립적이거나 재사용되기를 원한다면, `namespaced: true`라고 네임스페이스에 명시하면 됩니다. 모듈이 등록될 때, 해당 모듈의 모든 getter, 액션/변이는 자동으로 등록된 모듈의 경로를 기반으로 네임스페이스가 지정됩니다. 아래는 예시입니다:

``` js
const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,

      // 모듈 자산
      state: () => ({ ... }), // 모듈 상태는 이미 중첩되어 있고, 네임스페이스 옵션의 영향을 받지 않음
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      // 중첩 모듈
      modules: {
        // 부모 모듈로부터 네임스페이스를 상속받음
        myPage: {
          state: () => ({ ... }),
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },

        // 네임스페이스를 더 중첩
        posts: {
          namespaced: true,

          state: () => ({ ... }),
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})
```

네임스페이스의 getter와 액션은 지역화된  `getters`, `dispatch` 그리고 `commit`을 받습니다. 즉, 동일한 모듈 안에서 접두어 없이 모듈 자산을 사용할 수 있습니다. 네임스페이스 옵션 값을 바꾸어도 모듈 내부의 코드에는 영향을 미치지 않습니다.

#### 네임스페이스 모듈 내부에서 전역 자산 접근

전역 상태나 getter를 사용하고자 한다면, `rootState`와 `rootGetters`가 getter 함수의 3번째와 4번째 인자로 전달되고, 또한 action 함수에 전달된 'context' 객체의 속성으로도 노출됩니다.

전역 네임스페이스의 액션을 디스패치하거나 변이를 커밋하려면 `dispatch`와 `commit`에 3번째 인자로 `{ root: true }`를 전달하면 됩니다.

``` js
modules: {
  foo: {
    namespaced: true,

    getters: {
      // `getters`는 해당 모듈의 지역화된 getters
      // getters의 4번째 인자를 통해서 rootGetters 사용 가능
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 디스패치와 커밋도 해당 모듈의 지역화된 것
      // 전역 디스패치/커밋을 위한 `root` 옵션 설정 가능
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}
```

#### 네임스페이스 모듈에서 전역 액션 등록

네임스페이스 모듈에서 전역 액션을 등록하려면, `root: true`를 표시하고 `handler` 함수에 액션을 정의하면 됩니다. 아래는 예시입니다:

``` js
{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,

      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}
```

#### 헬퍼에서 네임스페이스 바인딩

`mapState`, `mapGetters`, `mapActions` 그리고 `mapMutations` 헬퍼에서 네임스페이스 모듈을 컴포넌트에 바인딩 할 때 조금 장황하게 됩니다.

``` js
computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}
```

이러한 경우에는 모듈의 네임스페이스 문자열을 헬퍼의 첫 번째 인수로 전달하여 해당 모듈을 컨텍스트로 사용하여 모든 바인딩을 할 수 있습니다. 위의 예시는 아래와 같이 단순화 할 수 있습니다.

``` js
computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}
```

또한 `createNamespacedHelpers`를 사용하여 네임스페이스 헬퍼를 생성할 수 있습니다. 전달된 네임스페이스 값으로 바인딩된 새로운 컴포넌트 바인딩 헬퍼를 가진 객체를 반환합니다.

``` js
import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // `some/nested/module`에서 찾음
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // `some/nested/module`에서 찾음
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}
```

#### 플러그인 개발자를 위한 주의 사항

개발자가 개발한 특정 모듈을 위해서 만든 [플러그인](plugins.md)을 사용자가 Vuex 저장소에 등록할 때 예측할 수 없는 네임스페이스 오류를 주의해야 합니다. 플러그인 사용자가 특정 모듈을 네임스페이스 모듈 하위에 추가하면 해당 모듈도 동일한 네임스페이스로 등록됩니다. 이러한 상황을 피하기 위해서 플러그인 옵션을 통해 네임스페이스 값을 전달받을 수 있어야 합니다.

``` js
// 플러그인 옵션을 통해 네임스페이스 값 전달
// 그리고 Vuex 플러그인 함수를 반환
export function createPlugin (options = {}) {
  return function (store) {
    // add namespace to plugin module's types
    const namespace = options.namespace || ''
    store.dispatch(namespace + 'pluginAction')
  }
}
```

### 동적 모듈 등록

`store.registerModule` 메소드로 저장소가 생성 된 **후에** 모듈을 등록 할 수 있습니다.

``` js
store.registerModule('myModule', {
  // ...
})

// `nested/myModule` 중첩 모듈 등록
store.registerModule(['nested', 'myModule'], {
  // ...
})
```

모듈의 상태는`store.state.myModule`와 `store.state.nested.myModule`로 노출 됩니다.

동적 모듈 등록을 사용하면 다른 Vue 플러그인도 애플리케이션의 저장소에 모듈을 연결하여 상태 관리에 Vuex를 활용할 수 있습니다. 예를 들어 [`vuex-router-sync`](https://github.com/vuejs/vuex-router-sync) 라이브러리는 동적으로 연결된 모듈에서 애플리케이션의 라우트 상태를 관리하여 vue-router와 vuex를 통합합니다.

`store.unregisterModule(moduleName)`을 사용하여 동적으로 등록 된 모듈을 제거할 수도 있습니다. 이 방법으로는 정적 모듈(저장소 생성시 선언 됨)을 제거 할 수 없습니다.

Server Side Rendered 앱에서 상태를 유지하는 것처럼 새 모듈을 등록할 때 이전 상태를 유지하고자 할 수 있습니다. `preserveState` 옵션을 사용하면 그렇게 할 수 있습니다: `store.registerModule('a', module, { preserveState: true })`

### 모듈 재사용

때로는 한 모듈에서 여러 인스턴스를 생성해야 할 수도 있습니다. 예를 들자면 다음과 같습니다:

- 동일 모듈을 사용하는 여러 저장소 생성 (예. [SSR 에서 싱글톤 상태 피하기](https://ssr.vuejs.org/en/structure.html#avoid-stateful-singletons) 에서 `runInNewContext` 옵션이 `false`나 `once`일 때)
- 동일 모듈을 동일 저장소에 여러 번 등록

일반 객체를 사용하여 모듈의 상태를 선언하면 상태 객체가 참조에 의해 공유되고 변이 될 때 교차 저장소/모듈의 상태 오염을 일으킵니다.

이것은 실제로 Vue 컴포넌트 내부의 `data`와 완전히 동일한 문제입니다. 그래서 해결책도 역시 동일합니다. 함수를 사용하여 모듈 상태를 선언합니다. (2.3.0 부터 지원함)

``` js
const MyReusableModule = {
  state: () => ({
    foo: 'bar'
  }),
  // 변이, 액션, getters...
}
```
