diff --git a/docs/LANGS.md b/docs/LANGS.md index ec8d59bae..2bef4f53b 100644 --- a/docs/LANGS.md +++ b/docs/LANGS.md @@ -3,4 +3,5 @@ * [2.0 - Français](fr/) * [2.0 - Русский](ru/) * [2.0 - 日本語](ja/) +* [2.0 - 한국어(Korean)](ko/) * [1.0 Docs](old/) diff --git a/docs/ko/README.md b/docs/ko/README.md new file mode 100644 index 000000000..f8a898044 --- /dev/null +++ b/docs/ko/README.md @@ -0,0 +1 @@ +{% include "./SUMMARY.md" %} diff --git a/docs/ko/SUMMARY.md b/docs/ko/SUMMARY.md new file mode 100644 index 000000000..828e7d213 --- /dev/null +++ b/docs/ko/SUMMARY.md @@ -0,0 +1,22 @@ +# Vuex + +> 참고: 이 문서는 vuex@2.x 을 기준으로 합니다. + +- [1.0 버전 문서를 보려면?](https://github.com/vuejs/vuex/tree/1.0/docs) +- [릴리즈 노트](https://github.com/vuejs/vuex/releases) +- [설치](installation.md) +- [Vuex가 무엇인가요?](intro.md) +- [시작하기](getting-started.md) +- 핵심 컨셉 + - [상태](state.md) + - [Getters](getters.md) + - [변이](mutations.md) + - [액션](actions.md) + - [모듈](modules.md) +- [애플리케이션 구조](structure.md) +- [플러그인](plugins.md) +- [Strict 모드](strict.md) +- [폼 핸들링](forms.md) +- [테스팅](testing.md) +- [핫 리로딩](hot-reload.md) +- [API 레퍼런스](api.md) diff --git a/docs/ko/actions.md b/docs/ko/actions.md new file mode 100644 index 000000000..e76d1b8a1 --- /dev/null +++ b/docs/ko/actions.md @@ -0,0 +1,175 @@ +# 액션 + +액션은 변이와 유사합니다. 몇가지 다른 점은, + +- 상태를 변이시키는 대신 액션으로 변이에 대한 커밋을 합니다. +- 작업에는 임의의 비동기 작업이 포함될 수 있습니다. + +간단한 액션을 등록합시다. + +``` js +const store = new Vuex.Store({ + state: { + count: 0 + }, + mutations: { + increment (state) { + state.count++ + } + }, + actions: { + increment (context) { + context.commit('increment') + } + } +}) +``` + +액션 핸들러는 저장소 인스턴스의 같은 메소드들/프로퍼티 세트를 드러내는 컨텍스트 객체를 받습니다. 그래서 `context.commit`을 호출하여 변이를 커밋하거나 `context.state`와 `context.getters`를 통해 상태와 getters에 접근 할 수 있습니다. 나중에 [모듈](modules.md)에서 이 컨텍스트 객체가 저장소 인스턴스 자체가 아닌 이유를 알 수 있습니다. + +실제로 (특히 `commit`를 여러 번 호출해야하는 경우)코드를 단순화하기 위해 ES2015 [전달인자 분해](https://github.com/lukehoban/es6features#destructuring)를 사용합니다. + +``` js +actions: { + increment ({ commit }) { + commit('increment') + } +} +``` + +### 디스패치 액션 + +액션은 `store.dispatch` 메소드로 시작됩니다. + +``` js +store.dispatch('increment') +``` + +처음 볼 때는 이상해 보일 수 있습니다. 카운트를 증가 시키려면 `store.commit('increment')`를 직접 호출하면 어떻습니까? 음, **돌연변이는 동기적** 이어야 한다는 것을 기억하십니까? 액션은 그렇지 않습니다. 액션 내에서 **비동기** 작업을 수행 할 수 있습니다. + +``` js +actions: { + incrementAsync ({ commit }) { + setTimeout(() => { + commit('increment') + }, 1000) + } +} +``` + +액션은 동일한 페이로드 타입과 객체 스타일의 디스패치를 지원합니다. + +``` js +// 페이로드와 함께 디스패치 +store.dispatch('incrementAsync', { + amount: 10 +}) + +// 객체와 함께 디스패치 +store.dispatch({ + type: 'incrementAsync', + amount: 10 +}) +``` + +액션의 좀 더 실용적인 예는 **비동기 API 호출** 과 **여러 개의 변이를 커밋** 하는 장바구니 결제입니다. + +``` js +actions: { + checkout ({ commit, state }, products) { + // 장바구니에 현재있는 항목을 저장하십시오. + const savedCartItems = [...state.cart.added] + + // 결제 요청을 보낸 후 장바구니를 비웁니다. + commit(types.CHECKOUT_REQUEST) + + // 상점 API는 성공 콜백 및 실패 콜백을 받습니다. + shop.buyProducts( + products, + // 요청 성공 핸들러 + () => commit(types.CHECKOUT_SUCCESS), + // 요청 실패 핸들러 + () => commit(types.CHECKOUT_FAILURE, savedCartItems) + ) + } +} +``` + +비동기 작업의 흐름을 수행하고 커밋하여 작업의 사이드이펙트(상태 변이)을 기록합니다. + +### 컴포넌트 내부에서 디스패치 액션 사용하기 + +`this.$store.dispatch('xxx')`를 사용하여 컴포넌트에서 액션을 디스패치하거나 컴포넌트 메소드를 `store.dispatch` 호출에 매핑하는 `mapActions` 헬퍼를 사용할 수 있습니다 (루트 `store` 주입 필요) : + +``` js +import { mapActions } from 'vuex' + +export default { + // ... + methods: { + ...mapActions([ + 'increment' // this.increment()을 this.$store.dispatch('increment')에 매핑 + ]), + ...mapActions({ + add: 'increment' // this.add()을 this.$store.dispatch('increment')에 매핑 + }) + } +} +``` + +### 액션 구성하기 + +액션은 종종 비동기적 입니다. 그러면 액션이 언제 완료되는지 어떻게 알 수 있습니까? 더 중요한 것은, 복잡한 비동기 흐름을 처리하기 위해 어떻게 여러 작업을 함께 구성 할 수 있습니까? + +가장 먼저 알아야 할 점은 `store.dispatch`가 트리거 된 액션 핸들러에 의해 반환된 Promise를 처리 할 수 있으며 Promise를 반환한다는 것입니다. + +``` js +actions: { + actionA ({ commit }) { + return new Promise((resolve, reject) => { + setTimeout(() => { + commit('someMutation') + resolve() + }, 1000) + }) + } +} +``` + +이렇게 할 수 있습니다. + +``` js +store.dispatch('actionA').then(() => { + // ... +}) +``` + +그리고 안에 또 다른 액션을 사용할 수 있습니다. + +``` js +actions: { + // ... + actionB ({ dispatch, commit }) { + return dispatch('actionA').then(() => { + commit('someOtherMutation') + }) + } +} +``` + +마지막으로, JavaScript 기능인 [async/await](https://tc39.github.io/ecmascript-asyncawait/)를 사용하면 다음과 같은 작업을 구성 할 수 있습니다. + +``` js +// getData() 및 getOtherData()가 Promise를 반환한다고 가정합니다. +actions: { + async actionA ({ commit }) { + commit('gotData', await getData()) + }, + async actionB ({ dispatch, commit }) { + await dispatch('actionA') // actionA가 끝나기를 기다립니다. + commit('gotOtherData', await getOtherData()) + } +} +``` + +> `store.dispatch`가 다른 모듈에서 여러 액션 핸들러를 트리거하는 것이 가능합니다. 이 경우 반환 된 값은 모든 트리거 된 처리기가 완료 되었을 때 처리되는 Promise입니다. diff --git a/docs/ko/api.md b/docs/ko/api.md new file mode 100644 index 000000000..ff0d66277 --- /dev/null +++ b/docs/ko/api.md @@ -0,0 +1,178 @@ +# API 레퍼런스 + +### Vuex.Store + +``` js +import Vuex from 'vuex' + +const store = new Vuex.Store({ ...options }) +``` + +### Vuex.Store 생성자 옵션 + +- **state** + + - 자료형: `Object` + + Vuex 저장소의 루트 상태 객체 입니다. + + [상세](state.md) + +- **mutations** + + - 자료형: `{ [type: string]: Function }` + + 저장소에 변이를 등록하십시오. 핸들러 함수는 항상 첫 번째 전달인자로 `state`를 받습니다 (모듈에 정의 된 경우 모듈 로컬 상태가됩니다). 두 번째 `payload` 전달인자가 있으면 처리합니다. + + [상세](mutations.md) + +- **actions** + + - 자료형: `{ [type: string]: Function }` + + 저장소에 액션을 등록하십시오. 핸들러 함수는 다음 속성을 노출하는 `context` 객체를받습니다. + + ``` js + { + state, // store.state와 같습니다. 또는 모듈에 있는 경우 로컬 상태 + rootState, // store.state와 같습니다. 모듈 안에만 존재합니다 + commit, // store.commit와 같습니다. + dispatch, // store.dispatch와 같습니다. + getters // store.getters와 같습니다. + } + ``` + + [상세](actions.md) + +- **getters** + + - 자료형: `{ [key: string]: Function }` + + 저장소에 getter를 등록하십시오. getter 함수는 다음 전달인자를 받습니다. + + ``` + state, // 모듈에 정의 된 경우 모듈 로컬 상태가됩니다. + getters, // store.getters와 같습니다. + rootState // store.state와 같습니다. + ``` + + 등록된 getter는 `store.getters`에 노출됩니다. + + [상세](getters.md) + +- **modules** + + - 자료형: `Object` + + 저장소에 병합될 하위 모듈을 포함하는 객체 입니다. + + ``` js + { + key: { + state, + mutations, + actions?, + getters?, + modules? + }, + ... + } + ``` + + 각 모듈은 루트 옵션과 비슷한 `state` 와 `mutations` 를 포함 할 수 있습니다. 모듈의 상태는 모듈의 키를 사용하여 저장소의 루트 상태에 연결됩니다. 모듈의 변이와 getter는 모듈의 로컬 상태를 루트 상태 대신 첫 번째 전달인자로 받으며 모듈 액션의 `context.state`도 로컬 상태를 가리 킵니다. + + [상세](modules.md) + +- **plugins** + + - 자료형: `Array` + + 저장소에 적용 할 플러그인 함수의 배열입니다. 플러그인은 저장소를 유일한 전달인자로 받아들이고 아웃바운드 데이터 지속성, 로깅 또는 디버깅을 위한 변이를 감시하거나 (인바운드 데이터 (예: 웹 소켓 또는 관찰 가능 항목)의 디스패치 변이) 감시할 수 있습니다. + + [상세](plugins.md) + +- **strict** + + - 자료형: `Boolean` + - 기본값: `false` + + Vuex 저장소를 strict 모드로 변경합니다. strict 모드에서 변이 핸들러 외부의 Vuex 상태에 대한 임의의 변이는 오류를 발생시킵니다. + + [상세](strict.md) + +### Vuex.Store 인스턴스 속성 + +- **state** + + - 자료형: `Object` + + 루트 상태. 읽기 전용 + +- **getters** + + - 자료형: `Object` + + 등록된 getters 입니다. 읽기 전용. + +### Vuex.Store 인스턴스 메소드 + +- **`commit(type: string, payload?: any) | commit(mutation: Object)`** + + 변이를 커밋합니다. [상세](mutations.md) + +- **`dispatch(type: string, payload?: any) | dispatch(action: Object)`** + + 액션을 디스패치 합니다. 모든 트리거된 액션 핸들러를 처리하는 Promise를 반환합니다. [상세](actions.md) + +- **`replaceState(state: Object)`** + + 저장소의 루트 상태를 바꿉니다. 상태에 대한 상호작용/시점 변경 목적으로 만 사용하십시오. + +- **`watch(getter: Function, cb: Function, options?: Object)`** + + getter 함수의 반환 값을 반응적으로 지켜보고 값이 변경되면 콜백을 호출합니다. getter는 저장소의 상태를 유일한 인수로받습니다. Vue의 `vm.$watch` 메소드와 같은 옵션을 취하는 옵션 객체를 받아들입니다. + + 감시를 중단하려면 반환된 핸들 함수를 호출하십시오. + +- **`subscribe(handler: Function)`** + + 저장소 변이를 구독합니다. `handler`는 모든 변이 이후 호출되고 변이 디스크립터와 변이 상태를 전달인자로 받습니다. + + ``` js + store.subscribe((mutation, state) => { + console.log(mutation.type) + console.log(mutation.payload) + }) + ``` + + 플러그인에서 가장 일반적으로 사용됩니다. [상세](plugins.md) + +- **`registerModule(path: string | Array, module: Module)`** + + 동적 모듈을 등록합니다. [상세](modules.md#dynamic-module-registration) + +- **`unregisterModule(path: string | Array)`** + + 동적 모듈을 해제 합니다. [상세](modules.md#dynamic-module-registration) + +- **`hotUpdate(newOptions: Object)`** + + 새 액션과 변이를 핫 스왑 합니다. [상세](hot-reload.md) + +### 컴포넌트 바인딩 헬퍼 + +- **`mapState(map: Array | Object): Object`** + + Vuex 저장소의 하위 트리를 반환하는 컴포넌트 계산 옵션을 만듭니다. [상세](state.md#the-mapstate-helper) + +- **`mapGetters(map: Array | Object): Object`** + + getter의 평가된 값을 반환하는 컴포넌트 계산 옵션을 만듭니다. [상세](getters.md#the-mapgetters-helper) + +- **`mapActions(map: Array | Object): Object`** + + 액션을 전달하는 컴포넌트 메소드 옵션을 만듭니다. [상세](actions.md#dispatching-actions-in-components) + +- **`mapMutations(map: Array | Object): Object`** + + 변이를 커밋하는 컴포넌트 메소드 옵션을 만듭니다. [상세](mutations.md#commiting-mutations-in-components) diff --git a/docs/ko/book.json b/docs/ko/book.json new file mode 100644 index 000000000..6e622745c --- /dev/null +++ b/docs/ko/book.json @@ -0,0 +1,19 @@ +{ + "gitbook": "2.x.x", + "plugins": ["edit-link", "prism", "-highlight", "github"], + "pluginsConfig": { + "edit-link": { + "base": "https://github.com/vuejs/vuex/tree/dev/docs", + "label": "Edit This Page" + }, + "github": { + "url": "https://github.com/vuejs/vuex/" + } + }, + "links": { + "sharing": { + "facebook": false, + "twitter": false + } + } +} diff --git a/docs/ko/forms.md b/docs/ko/forms.md new file mode 100644 index 000000000..e0d376b36 --- /dev/null +++ b/docs/ko/forms.md @@ -0,0 +1,57 @@ +# 폼 핸들링 + +strict 모드로 Vuex를 사용하는 경우 Vuex에 포함된 부분에 `v-model`을 사용하는 것은 약간 까다로울 수 있습니다. + +``` html + +``` + +`obj`가 저장소에서 객체를 반환하는 계산된 속성이라면, 여기에있는 `v-model`은 사용자가 입력 할 때 `obj.message`를 직접 변경하려고 합니다. strict 모드에서는 Vuex 변이 처리기 내부에서 변이가 수행되지 않으므로 오류가 발생합니다. + +그것을 다루는 "Vuex 방식"은 ``의 값을 바인딩하고 `input` 또는 `change` 이벤트에 대한 액션을 호출하는 것 입니다. + +``` html + +``` +``` js +// ... +computed: { + ...mapState({ + message: state => state.obj.message + }) +}, +methods: { + updateMessage (e) { + this.$store.commit('updateMessage', e.target.value) + } +} +``` + +변이에 대한 핸들러 입니다. + +``` js +// ... +mutations: { + updateMessage (state, message) { + state.obj.message = message + } +} +``` + +### 양방향 계산된 속성 + +틀림없이, 위의 내용은 `v-model` + 지역 상태보다 좀더 장황 해졌고, `v-model`의 유용한 기능 중 일부를 잃어 버렸습니다. 다른 방법은 setter를 사용하여 양방향 계산된 속성을 사용하는 것입니다. + +``` js +// ... +computed: { + message: { + get () { + return this.$store.state.obj.message + }, + set (value) { + this.$store.commit('updateMessage', value) + } + } +} +``` diff --git a/docs/ko/getters.md b/docs/ko/getters.md new file mode 100644 index 000000000..39e5042e7 --- /dev/null +++ b/docs/ko/getters.md @@ -0,0 +1,91 @@ +# Getters + +때로는 저장소 상태를 기반하는 상태를 계산해야 할 수도 있습니다.(예: 아이템 리스트를 필터링하고 계산) + +``` js +computed: { + doneTodosCount () { + return this.$store.state.todos.filter(todo => todo.done).length + } +} +``` + +둘 이상의 컴포넌트가 이를 사용 해야하는 경우 함수를 복제하거나 공유된 헬퍼를 추출하여 여러 위치에서 가져와야합니다. 둘 다 이상적이지 않습니다. + +Vuex를 사용하면 저장소에서 "getters"를 정의 할 수 있습니다(저장소의 계산된 속성으로 생각됩니다). Getters는 첫 번째 전달인자로 상태를 받습니다. + +``` js +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) + } + } +}) +``` + +getters는 `store.getters` 객체에 노출 됩니다. + +``` js +store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }] +``` + +Getters는 두 번째 전달인자로 다른 getter도 받게됩니다. + +``` js +getters: { + // ... + doneTodosCount: (state, getters) => { + return getters.doneTodos.length + } +} +``` + +``` js +store.getters.doneTodosCount // -> 1 +``` + +이제 모든 컴포넌트에서 쉽게 사용할 수 있습니다. + +``` js +computed: { + doneTodosCount () { + return this.$store.getters.doneTodosCount + } +} +``` + +### `mapGetters` 헬퍼 + +`mapGetters` 헬퍼는 저장소 getter를 로컬 계산된 속성에 매핑합니다. + +``` js +import { mapGetters } from 'vuex' + +export default { + // ... + computed: { + // getter를 객체 전파 연산자로 계산하여 추가합니다. + ...mapGetters([ + 'doneTodosCount', + 'anotherGetter', + // ... + ]) + } +} +``` + +getter를 다른 이름으로 매핑하려면 객체를 사용합니다. + +``` js +...mapGetters({ + // this.doneCount를 store.getters.doneTodosCount에 매핑하십시오. + doneCount: 'doneTodosCount' +}) +``` diff --git a/docs/ko/getting-started.md b/docs/ko/getting-started.md new file mode 100644 index 000000000..260e37d95 --- /dev/null +++ b/docs/ko/getting-started.md @@ -0,0 +1,43 @@ +# 시작하기 + +모든 Vuex 애플리케이션의 중심에는 **store** 가 있습니다. "저장소"는 기본적으로 애플리케이션 **상태** 를 보유하고있는 컨테이너입니다. Vuex 저장소가 일반 전역 개체와 두 가지 다른 점이 있습니다. + +1. Vuex store는 반응형 입니다. Vue 컴포넌트는 상태를 검색할 때 저장소의 상태가 변경되면 효율적으로 대응하고 업데이트합니다. +2. 저장소의 상태를 직접 변경할 수 없습니다. 저장소의 상태를 변경하는 유일한 방법은 명시적인 **커밋을 이용한 변이** 입니다. 이렇게하면 모든 상태에 대한 추적이 가능한 기록이 남을 수 있으며 툴를 사용하여 앱을 더 잘 이해할 수 있습니다. + +### 가장 단순한 저장소 + +> **참고:** 모든 예제는 ES2015 문법을 사용합니다. 사용하고 있지 않은 경우 [꼭 사용해야 합니다!](https://babeljs.io/docs/learn-es2015/) + +Vuex를 [설치](installation.md)한 후 저장소를 만들어 봅시다. 매우 간단합니다. 초기 상태 객체와 일부 변이를 제공하십시오. + +``` js +// 모듈 시스템을 사용하는 경우 Vue.use(Vuex)를 먼저 호출해야합니다. + +const store = new Vuex.Store({ + state: { + count: 0 + }, + mutations: { + increment (state) { + state.count++ + } + } +}) +``` + +이제 state 객체에 `store.state`로 접근하여 `store.commit` 메소드로 상태 변경을 트리거 할 수 있습니다. + +``` js +store.commit('increment') + +console.log(store.state.count) // -> 1 +``` + +다시 말해, `store.state.count`를 직접 변경하는 대신 변이를 수행하는 이유는 명시적으로 추적을 하기 때문입니다. 이 간단한 규칙에 따라 의도를보다 명확하게 표현할 수 있으므로 코드를 읽을 때 상태 변화를 더 잘 지켜볼 수 있습니다. 또한 모든 변이를 기록하고 상태 스냅샷을 저장하거나 시간 흐름에 따라 디버깅을 할 수 있는 도구를 제공합니다. + +컴포넌트 안에서 저장소 상태를 사용하는 것은 단순히 계산된 속성 내에서 상태를 반환하는 것입니다. 변경을 트리거하는 것은 컴포넌트 메소드에서 변경을 커밋하는 것을 의미합니다. + +다음은 [가장 기본적인 Vuex 카운터 앱](https://jsfiddle.net/yyx990803/n9jmu5v7/)의 예입니다. + +이제, 우리는 각 핵심 개념에 대해 더 자세히 설명 할 것입니다. [State](state.md)부터 시작해 보겠습니다. diff --git a/docs/ko/hot-reload.md b/docs/ko/hot-reload.md new file mode 100644 index 000000000..8ae0e8369 --- /dev/null +++ b/docs/ko/hot-reload.md @@ -0,0 +1,44 @@ +# 핫 리로딩 + +Vuex는 Webpack의 [핫 모듈 변경 API](https://webpack.github.io/docs/hot-module-replacement.html)를 사용하여 개발 중에 핫 리로드 변이, 모듈, 액션 및 getter를 지원합니다. [browserify-hmr](https://github.com/AgentME/browserify-hmr/) 플러그인으로 Browserify에서 사용할 수도 있습니다. + +변이와 모듈의 경우, `store.hotUpdate()` API 메소드를 사용할 필요가 있습니다. + +``` js +// store.js +import Vue from 'vue' +import Vuex from 'vuex' +import mutations from './mutations' +import moduleA from './modules/a' + +Vue.use(Vuex) + +const state = { ... } + +const store = new Vuex.Store({ + state, + mutations, + modules: { + a: moduleA + } +}) + +if (module.hot) { + // 액션과 변이를 핫 모듈로 받아 들인다. + module.hot.accept(['./mutations', './modules/a'], () => { + // 업데이트 된 모듈은 babel 6 모듈 출력으로 인해 + // .default를 여기에 추가해야합니다. + const newMutations = require('./mutations').default + const newModuleA = require('./modules/a').default + // 새로운 액션과 변이로 바꿉니다. + store.hotUpdate({ + mutations: newMutations, + modules: { + a: newModuleA + } + }) + }) +} +``` + +[counter-hot 예제](https://github.com/vuejs/vuex/tree/dev/examples/counter-hot)로 핫 리로드를 확인하십시오. diff --git a/docs/ko/images/flow.png b/docs/ko/images/flow.png new file mode 100644 index 000000000..fd9b97d59 Binary files /dev/null and b/docs/ko/images/flow.png differ diff --git a/docs/ko/images/vuex.png b/docs/ko/images/vuex.png new file mode 100644 index 000000000..018129947 Binary files /dev/null and b/docs/ko/images/vuex.png differ diff --git a/docs/ko/installation.md b/docs/ko/installation.md new file mode 100644 index 000000000..fd4b21f37 --- /dev/null +++ b/docs/ko/installation.md @@ -0,0 +1,51 @@ +# 설치 + +### 직접 다운로드 / CDN + +[https://unpkg.com/vuex](https://unpkg.com/vuex) + + +[Unpkg.com](https://unpkg.com)은 NPM 기반 CDN 링크를 제공합니다. 위의 링크는 항상 NPM의 최신 릴리스를 가리킵니다. `https://unpkg.com/vuex@2.0.0`과 같은 URL을 통해 특정 버전/태그를 사용할 수도 있습니다. + + +Vue 뒤에 `vuex`를 추가하면 자동으로 설치됩니다: + +``` html + + +``` + +### NPM + +``` bash +npm install vuex +``` + +### Yarn + +``` bash +yarn add vuex +``` + +모듈 시스템과 함께 사용하면 `Vue.use()`를 통해 Vuex를 명시적으로 추가해야 합니다. + +``` js +import Vue from 'vue' +import Vuex from 'vuex' + +Vue.use(Vuex) +``` + +전역 스크립트 태그를 사용할 때는 이 작업을 할 필요가 없습니다. + +### 개발용 빌드 + +최신 dev 빌드를 사용하고 싶은 경우 직접 GitHub에서 클론하고 `vuex`를 직접 빌드 해야합니다. + + +``` bash +git clone https://github.com/vuejs/vuex.git node_modules/vuex +cd node_modules/vuex +npm install +npm run build +``` diff --git a/docs/ko/intro.md b/docs/ko/intro.md new file mode 100644 index 000000000..57cf0d308 --- /dev/null +++ b/docs/ko/intro.md @@ -0,0 +1,63 @@ +# Vuex가 무엇인가요? + +Vuex는 Vue.js 애플리케이션에 대한 **상태 관리 패턴 + 라이브러리** 입니다. 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 하며 예측 가능한 방식으로 상태를 변경할 수 있습니다. 또한 Vue의 공식 [devtools 확장 프로그램](https://github.com/vuejs/vue-devtools)과 통합되어 설정 시간이 필요 없는 디버깅 및 상 태 스냅 샷 내보내기/가져오기와 같은 고급 기능을 제공합니다. + +### "상태 관리 패턴"이란 무엇인가요? + +간단한 Vue 카운터 앱부터 시작 해보겠습니다. + +``` js +new Vue({ + // 상태 + data () { + return { + count: 0 + } + }, + // 뷰 + template: ` +
{{ count }}
+ `, + // 액션 + methods: { + increment () { + this.count++ + } + } +}) +``` + +다음과 같은 기능을 가진 앱입니다. + +- **상태** 는 앱을 작동하는 원본 소스 입니다. +- **뷰** 는 **상태의** 선언적 매핑입니다. +- **액션** 은 **뷰** 에서 사용자 입력에 대해 반응적으로 상태를 바꾸는 방법입니다. + +이것은 "단방향 데이터 흐름" 개념의 매우 단순한 도표입니다. + +

+ +

+ +그러나 **공통의 상태를 공유하는 여러 컴포넌트** 가 있는 경우 단순함이 빠르게 저하됩니다. + +- 여러 뷰는 같은 상태에 의존합니다. +- 서로 다른 뷰의 작업은 동일한 상태를 반영해야 할 수 있습니다. + +첫번째 문제의 경우, 지나치게 중첩된 컴포넌트는 통과하는 prop는 장황할 수 있으며 형제 컴포넌트에서는 작동하지 않습니다. 두번째 문제의 경우 직접 부모/자식 인스턴스를 참조하거나 이벤트를 통해 상태의 여러 복사본을 변경 및 동기화 하려는 등의 해결 방법을 사용해야 합니다. 이러한 패턴은 모두 부서지기 쉽고 유지보수가 불가능한 코드로 빠르게 변경됩니다. + +그렇다면 컴포넌트에서 공유된 상태를 추출하고 이를 전역 싱글톤으로 관리해야 합니다. 이를 통해 우리의 컴포넌트 트리는 커다란 "뷰"가 되며 모든 컴포넌트는 트리에 상관없이 상태에 액세스하거나 동작을 트리거 할 수 있습니다! + +또한 상태 관리 및 특정 규칙 적용과 관련된 개념을 정의하고 분리함으로써 코드의 구조와 유지 관리 기능을 향상시킵니다. + +이는 [Flux](https://facebook.github.io/flux/docs/overview.html), [Redux](http://redux.js.org/), [The Elm Architecture](https://guide.elm-lang.org/architecture/)에서 영감을 받은 Vuex의 기본 아이디어 입니다. 다른 패턴과 달리 Vuex는 Vue.js가 효율적인 업데이트를 위해 세분화된 반응 시스템을 활용하도록 특별히 고안된 라이브러리입니다. + +![vuex](./images/vuex.png) + +### 언제 사용해야 하나요? + +Vuex는 공유된 상태 관리를 처리하는 데 유용하지만, 개념에 대한 이해와 시작하는 비용도 함께 듭니다. 그것은 단기간과 장기간 생산성 간의 기회비용이 있습니다. + +대규모 SPA를 구축하지 않고 Vuex로 바로 뛰어 들었다면, 시간이 오래 걸리고 힘든일일 것입니다. 이것은 일반 적인 일입니다. 앱이 단순하다면 Vuex없이는 괜찮을 것입니다. 간단한 [글로벌 이벤트 버스](http://vuejs.org/guide/components.html#Non-Parent-Child-Communication)만 있으면됩니다. 그러나 중대형 규모의 SPA를 구축하는 경우 Vue컴포넌트 외부의 상태를 보다 잘 처리할 수 있는 방법을 생각하게 될 가능성이 있으며 Vuex는 자연스럽게 선택할 수 있는 단계가 될 것입니다. Redux의 저자인 Dan Abramov의 좋은 인용이 있습니다. + +> Flux 라이브러리는 안경과 같습니다. 필요할 때 알아볼 수 있습니다. diff --git a/docs/ko/modules.md b/docs/ko/modules.md new file mode 100644 index 000000000..7ee336420 --- /dev/null +++ b/docs/ko/modules.md @@ -0,0 +1,138 @@ +# 모듈 + +단일 상태 트리를 사용하기 때문에 애플리케이션의 모든 상태가 하나의 큰 객체 안에 포함됩니다. 그러나 규모가 커짐에 따라 저장소는 매우 비대해질 수 있습니다. + +이를 위해 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: { + incrementIfOdd ({ state, commit }) { + if (state.count % 2 === 1) { + commit('increment') + } + } + } +} +``` + +또한, 모듈 getters 내부, 루트 상태는 그들의 세 번째 전달인자로 노출됩니다. + +``` js +const moduleA = { + // ... + getters: { + sumWithRootCount (state, getters, rootState) { + return state.count + rootState.count + } + } +} +``` + +### 네임스페이스 + +모듈 내의 액션, 변이 및 getter는 여전히 **전역 네임 스페이스** 아래에 등록됩니다. 여러 모듈이 동일한 변이/액션 유형에 반응 할 수 있습니다. 이름 앞에 접두사 또는 접미사를 붙이면 이름 충돌을 피하기 위해 모듈 자신의 네임스페이스를 직접 지정할 수 있습니다. 그리고 알 수 없는 환경에서 사용될 재사용 가능한 Vuex 모듈을 작성하는 경우라면 반드시 사용해야 합니다. 예를 들어,`todos` 모듈을 만들고 싶은 경우 + +``` js +// types.js + +// getter, 액션, 변이의 이름을 상수로 정의하고 +// 모듈 이름 `todos` 접두어를 붙입니다 +export const DONE_COUNT = 'todos/DONE_COUNT' +export const FETCH_ALL = 'todos/FETCH_ALL' +export const TOGGLE_DONE = 'todos/TOGGLE_DONE' +``` + +``` js +// modules/todos.js +import * as types from '../types' + +// 접두어로 된 이름을 사용하여 getter, 액션 및 변이 정의 +const todosModule = { + state: { todos: [] }, + + getters: { + [types.DONE_COUNT] (state) { + // ... + } + }, + + actions: { + [types.FETCH_ALL] (context, payload) { + // ... + } + }, + + mutations: { + [types.TOGGLE_DONE] (state, payload) { + // ... + } + } +} +``` + +### 동적 모듈 등록 + +`store.registerModule` 메소드로 저장소가 생성 된 **후에** 모듈을 등록 할 수 있습니다. + +``` js +store.registerModule('myModule', { + // ... +}) +``` + +모듈의 상태는`store.state.myModule`으로 노출 됩니다. + +동적 모듈 등록을 사용하면 다른 Vue 플러그인도 애플리케이션의 저장소에 모듈을 연결하여 상태 관리에 Vuex를 활용할 수 있습니다. 예를 들어 [`vuex-router-sync`](https://github.com/vuejs/vuex-router-sync) 라이브러리는 동적으로 연결된 모듈에서 애플리케이션의 라우트 상태를 관리하여 vue-router와 vuex를 통합합니다. + +`store.unregisterModule(moduleName)`을 사용하여 동적으로 등록 된 모듈을 제거할 수도 있습니다. 이 방법으로는 정적 모듈(저장소 생성시 선언 됨)을 제거 할 수 없습니다. diff --git a/docs/ko/mutations.md b/docs/ko/mutations.md new file mode 100644 index 000000000..738a0498e --- /dev/null +++ b/docs/ko/mutations.md @@ -0,0 +1,185 @@ +# 변이 + +Vuex 저장소에서 실제로 상태를 변경하는 유일한 방법은 변이하는 것입니다. Vuex 변이는 이벤트와 매우 유사합니다. 각 변이에는 **타입** 문자열 **핸들러** 가 있습니다. 핸들러 함수는 실제 상태 수정을 하는 곳이며, 첫 번째 전달인자로 상태를받습니다. + +``` js +const store = new Vuex.Store({ + state: { + count: 1 + }, + mutations: { + increment (state) { + // 상태 변이 + state.count++ + } + } +}) +``` + +변이 핸들러를 직접 호출 할 수는 없습니다. 이 옵션은 이벤트 등록과 비슷합니다. "타입이 `increment`인 변이가 발생하면이 핸들러를 호출합니다." 변이 핸들러를 호출하려면 해당 타입과 함께 **store.commit** 을 호출해야합니다. + +``` js +store.commit('increment') +``` + +### 페이로드를 가진 커밋 + +변이에 대해 **payload** 라고하는 `store.commit`에 추가 전달인자를 사용 할 수 있습니다. + +``` js +// ... +mutations: { + increment (state, n) { + state.count += n + } +} +``` +``` js +store.commit('increment', 10) +``` + +대부분의 경우 페이로드는 여러 필드를 포함할 수 있는 객체여야하며 기록 된 변이는 더 이해하기 쉽습니다. + +``` js +// ... +mutations: { + increment (state, payload) { + state.count += payload.amount + } +} +``` +``` js +store.commit('increment', { + amount: 10 +}) +``` + +### 객체 스타일 커밋 + +변이를 커밋하는 또 다른 방법은 `type` 속성을 가진 객체를 직접 사용하는 것입니다. + +``` js +store.commit({ + type: 'increment', + amount: 10 +}) +``` + +객체 스타일 커밋을 사용할 때 전체 객체는 변이 핸들러에 페이로드로 전달되므로 핸들러는 동일하게 유지됩니다. + +``` js +mutations: { + increment (state, payload) { + state.count += payload.amount + } +} +``` + +### 조용한 커밋 + +> 참고: 이것은 devtools에서 변이 필터링을 구현하면 더 이상 사용되지 않을 예정입니다. + +기본적으로 커밋 된 모든 변이는 플러그인(예: devtools)에 전송됩니다. 그러나 일부 시나리오에서는 플러그인이 모든 상태 변경을 기록하는 것을 원하지 않을 수도 있습니다. 단기간에 저장소에 대한 여러 커밋나 폴링 된 것이 항상 추적될 필요는 없습니다. 이 경우 세 번째 인수를 `store.commit`에 전달하여 플러그인에서 특정 변이를 "조용하게" 할 수 있습니다. + +``` js +store.commit('increment', { + amount: 1 +}, { silent: true }) + +// 객체 스타일 커밋 +store.commit({ + type: 'increment', + amount: 1 +}, { silent: true }) +``` + +### Vue의 반응성 규칙을 따르는 변이 + +Vuex 저장소의 상태는 Vue에 의해 반응하므로, 상태를 변경하면 상태를 관찰하는 Vue 컴포넌트가 자동으로 업데이트됩니다. 이것은 또한 Vuex 변이가 일반 Vue로 작업 할 때 동일한 반응성에 대한 경고를 받을 수 있음을 의미합니다. + +1. 원하는 모든 필드에 앞서 저장소를 초기화하는 것이 좋습니다. + +2. 객체에 새 속성을 추가할 때 다음 중 하나를 수행해야합니다. + + - `Vue.set(obj, 'newProp', 123)`을 사용하거나, + + - 객체를 새로운 것으로 교체하십시오. 예를 들어, 3 단계 [객체 전파 문법](https://github.com/sebmarkbage/ecmascript-rest-spread)을 사용하면 다음과 같이 작성할 수 있습니다. + + ``` js + state.obj = { ...state.obj, newProp: 123 } + ``` + +### 변이 타입에 상수 사용 + +다양한 Flux 구현에서 변이 유형에 상수를 사용하는 것은 일반인 패턴입니다. 이를 통해 코드는 linter와 같은 툴링을 활용할 수 있으며 모든 상수를 단일 파일에 저장하면 공동 작업자가 전체 애플리케이션에서 어떤 변이가 가능한지 한눈에 파악할 수 있습니다. + +``` js +// mutation-types.js +export const SOME_MUTATION = 'SOME_MUTATION' +``` + +``` js +// store.js +import Vuex from 'vuex' +import { SOME_MUTATION } from './mutation-types' + +const store = new Vuex.Store({ + state: { ... }, + mutations: { + // ES2015에서 계산 된 프로퍼티 이름 기능을 사용하여 + // 상수를 함수 이름으로 사용할 수 있습니다 + [SOME_MUTATION] (state) { + // 변이 상태 + } + } +}) +``` + +상수를 사용할지 여부는 대부분 환경 설정입니다. 개발자가 많은 대규모 프로젝트에서 유용할 수 있지만, 이는 완전히 선택 사항입니다. + +### 변이는 무조건 동기적이어야 합니다. + +기억 해야할 한 가지 중요한 규칙은 **변이 핸들러 함수는 동기적** 이어야 한다는 것입니다. 왜 그럴까요? 다음 예제를 확인해보십시오. + +``` js +mutations: { + someMutation (state) { + api.callAsyncMethod(() => { + state.count++ + }) + } +} +``` + +이제 우리가 앱을 디버깅하고 devtool의 돌연변이 로그를 보고 있다고 상상해보십시오. 기록 된 모든 변이에 대해 devtool은 상태의 "이전" 및 "이후" 스냅 샷을 캡처 해야 합니다. 그러나 위의 예제 변이 내의 비동기 콜백은 불가능합니다. 변이가 커밋 되었을 때 콜백은 아직 호출되지 않으며, 콜백이 실제로 호출 될 시기를 devtool이 알 수 있는 방법이 없습니다. 콜백에서 수행 된 모든 상태 변이는 본질적으로 추적 할 수 없습니다! + +### 컴포넌트 안에서 변이 커밋하기 + +`this.$store.commit('xxx')`를 사용하여 컴포넌트에서 변이를 수행하거나 컴포넌트 메소드를 `store.commit` 호출에 매핑하는 `mapMutations` 헬퍼를 사용할 수 있습니다 (루트 `store` 주입 필요) + +``` js +import { mapMutations } from 'vuex' + +export default { + // ... + methods: { + ...mapMutations([ + 'increment' // this.increment()를 this.$store.commit('increment')에 매핑합니다. + ]), + ...mapMutations({ + add: 'increment' // this.add()를 this.$store.commit('increment')에 매핑합니다. + }) + } +} +``` + +### 액션에서 사용 + +비동기성이 상태의 변이와 결합하면 프로그램을 파악하기가 매우 어려워 질 수 있습니다. 예를 들어 상태를 변경하는 두 가지 비동기 콜백 메소드를 호출할 때 호출되는 시점과 먼저 호출 된 콜백을 어떻게 알 수 있습니까? 이것이 우리가 두 개념을 분리하려는 이유입니다. Vuex에서 **변이는 동기적으로 트랜잭션합니다.** + +``` js +store.commit('increment') +// "increment" 변이가 일으킬 수 있는 모든 상태 변화는 이 순간에 이루어져야합니다. +``` + +비동기 작업을 처리하기 위한 [액션](actions.md)를 소개합시다. diff --git a/docs/ko/plugins.md b/docs/ko/plugins.md new file mode 100644 index 000000000..495112e8f --- /dev/null +++ b/docs/ko/plugins.md @@ -0,0 +1,121 @@ +# 플러그인 + +Vuex 저장소는 각 변이에 대한 훅을 노출하는 `plugins` 옵션을 허용합니다. Vuex 플러그인은 저장소를 유일한 전달인자로 받는 함수입니다. + +``` js +const myPlugin = store => { + // 저장소가 초기화 될 때 불립니다. + store.subscribe((mutation, state) => { + // 매 변이시마다 불립니다. + // 변이는 { type, payload } 포맷으로 제공됩니다. + }) +} +``` + +그리고 다음과 같이 사용할 수 있습니다. + +``` js +const store = new Vuex.Store({ + // ... + plugins: [myPlugin] +}) +``` + +### 플러그인 내부에서 변이 커밋하기 + +플러그인은 상태를 직접 변이할 수 없습니다. 컴포넌트와 마찬가지로 변이를 커밋하여 변경을 트리거 할 수 있습니다. + +변이을 커밋함으로써 플러그인을 사용하여 데이터 소스를 저장소에 동기화 할 수 있습니다. 예를 들어, websocket 데이터 소스를 저장소에 동기화하려면 (이는 사실 인위적인 예제입니다. 실제로 `createPlugin` 함수는 더 복잡한 작업을 위해 몇 가지 추가 옵션을 필요로 할 수 있습니다) + +``` js +export default function createWebSocketPlugin (socket) { + return store => { + socket.on('data', data => { + store.commit('receiveData', data) + }) + store.subscribe(mutation => { + if (mutation.type === 'UPDATE_DATA') { + socket.emit('update', mutation.payload) + } + }) + } +} +``` + +``` js +const plugin = createWebSocketPlugin(socket) + +const store = new Vuex.Store({ + state, + mutations, + plugins: [plugin] +}) +``` + +### 상태 스냅샷 가져오기 + +때로는 플러그인이 상태의 "스냅샷"을 얻고자 할 수 있으며, 또한 변이 이후 상태와 변이 이전 상태를 비교할 수 있습니다. 이를 달성하기 위해서는 상태 객체에 대한 깊은 복사를 수행해야합니다 : + +``` js +const myPluginWithSnapshot = store => { + let prevState = _.cloneDeep(store.state) + store.subscribe((mutation, state) => { + let nextState = _.cloneDeep(state) + + // prevState와 nextState를 비교하십시오. + + // 다음 변이를 위한 상태 저장 + prevState = nextState + }) +} +``` + +**상태 스냅 샷을 사용하는 플러그인은 개발 중에 만 사용해야합니다.** Webpack 또는 Browserify를 사용하는 경우 빌드 도구가 이를 처리 할 수 있습니다. + +``` js +const store = new Vuex.Store({ + // ... + plugins: process.env.NODE_ENV !== 'production' + ? [myPluginWithSnapshot] + : [] +}) +``` + +플러그인은 기본적으로 사용됩니다. 배포를 위해서는 Webpack의 [DefinePlugin](https://webpack.github.io/docs/list-of-plugins.html#defineplugin) 또는 [envify](https://github.com/hughsk/envify)가 필요합니다. Browserify가 `process.env.NODE_ENV !== 'production'`의 값을 최종 빌드를 위해 `false`로 변환합니다. + + +### 내장 로거 플러그인 + +> [vue-devtools](https://github.com/vuejs/vue-devtools)를 사용하고 있으면 필요 없을 수 있습니다. + +Vuex에는 일반적인 디버깅을 위한 로거 플러그인이 함께 제공됩니다. + +``` js +import createLogger from 'vuex/dist/logger' + +const store = new Vuex.Store({ + plugins: [createLogger()] +}) +``` + +`createLogger` 함수는 몇 가지 옵션을 가질 수 있습니다. + +``` js +const logger = createLogger({ + collapsed: false, // 로그를 가지는 변이 자동 확장 + transformer (state) { + // 로깅하기전 상태를 변이 하십시오. + // 예를 들어 특정 하위 트리만 반환합니다. + return state.subTree + }, + mutationTransformer (mutation) { + // 변이는 { type, payload }의 포맷으로 기록됩니다. + // 원하는 포맷으로 변경할 수 있습니다. + return mutation.type + } +}) +``` + +로거 파일은`