Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(ru): add Vuex 4.x translation #2205

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion docs/.vitepress/config.js
Original file line number Diff line number Diff line change
@@ -23,6 +23,11 @@ module.exports = {
lang: 'pt-BR',
title: 'Vuex',
description: 'Gerenciamento de Estado Centralizado para Vue.js'
},
'/ru/': {
lang: 'ru',
title: 'Vuex',
description: 'Централизованное управление состоянием для Vue.js'
}
},

@@ -275,7 +280,66 @@ module.exports = {
]
}
]
}
},

'/ru/': {
label: 'Русский',
selectText: 'Переводы',
editLinkText: 'Изменить эту страницу на GitHub',
lastUpdated: 'Последнее обновление',

nav: [
{ text: 'Руководство', link: '/ru/guide/' },
{ text: 'Справочник API', link: '/ru/api/' },
{ text: 'История изменений', link: 'https://github.com/vuejs/vuex/releases' },
{
text: 'v4.x',
items: [
{ text: 'v3.x', link: 'https://v3.vuex.vuejs.org/ru/' }
]
}
],

sidebar: [
{
text: 'Введение',
children: [
{ text: 'Что такое Vuex?', link: '/ru/' },
{ text: 'Установка', link: '/ru/installation' },
{ text: 'Введение', link: '/ru/guide/' }
]
},
{
text: 'Основные понятия',
children: [
{ text: 'Состояние', link: '/ru/guide/state' },
{ text: 'Геттеры', link: '/ru/guide/getters' },
{ text: 'Мутации', link: '/ru/guide/mutations' },
{ text: 'Действия', link: '/ru/guide/actions' },
{ text: 'Модули', link: '/ru/guide/modules' }
]
},
{
text: 'Продвинутые темы',
children: [
{ text: 'Структура приложения', link: '/ru/guide/structure' },
{ text: 'Composition API', link: '/ru/guide/composition-api' },
{ text: 'Плагины', link: '/ru/guide/plugins' },
{ text: 'Строгий режим (strict mode)', link: '/ru/guide/strict' },
{ text: 'Работа c формами', link: '/ru/guide/forms' },
{ text: 'Тестирование', link: '/ru/guide/testing' },
{ text: 'Горячая перезагрузка', link: '/ru/guide/hot-reload' },
{ text: 'Поддержка TypeScript', link: '/ru/guide/typescript-support' },
]
},
{
text: 'Руководство по миграции',
children: [
{ text: 'Миграция на 4.0 с 3.x', link: '/ru/guide/migrating-to-4-0-from-3-x' }
]
}
]
},
}
}
}
6,998 changes: 6,998 additions & 0 deletions docs/public/ru/flow.ai

Large diffs are not rendered by default.

Binary file added docs/public/ru/flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3,475 changes: 3,475 additions & 0 deletions docs/public/ru/vuex.ai

Large diffs are not rendered by default.

Binary file added docs/public/ru/vuex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
398 changes: 398 additions & 0 deletions docs/ru/api/index.md

Large diffs are not rendered by default.

180 changes: 180 additions & 0 deletions docs/ru/guide/actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Действия

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/c6ggR3cG" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

Действия — похожи на мутации с несколькими отличиями:

* Вместо того, чтобы напрямую менять состояние, действия инициируют мутации;
* Действия могут использоваться для асинхронных операций.

Зарегистрируем простое действие:

```js
const store = createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
increment(context) {
context.commit('increment')
}
}
})
```

Обработчики действий получают объект контекста, содержащий те же методы и свойства, что и сам экземпляр хранилища, так что можно вызвать `context.commit` для инициирования мутации или обратиться к состоянию и геттерам через `context.state` и `context.getters`. Можно даже вызывать другие действия через `context.dispatch`. Позднее при рассмотрении [модулей](modules.md) будет видно, что этот контекст — не то же самое, что экземпляр хранилища.

На практике для упрощения кода часто используется [деструктуризация аргументов](https://github.com/lukehoban/es6features#destructuring) из ES2015 (особенно при необходимости многократного вызова `commit`):

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

## Диспетчеризация действий

Действия запускаются методом `store.dispatch`:

```js
store.dispatch('increment')
```

На первый взгляд может выглядеть глупо: если хочется увеличить значение count, почему бы просто не вызвать `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')` или используя вспомогательную функцию `mapActions`, создающую локальные псевдонимы для действий в виде методов компонента (требуется наличие корневого `$store`):

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

export default {
// ...
methods: {
...mapActions([
'increment' // проксирует `this.increment()` в `this.$store.dispatch('increment')`

// `mapActions` также поддерживают нагрузку (payloads):
'incrementBy' // проксирует `this.incrementBy(amount)` в `this.$store.dispatch('incrementBy', amount)`
]),
...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')
})
}
}
```

Наконец, если использовать [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, разрешающийся после разрешения всех вызванных обработчиков.
62 changes: 62 additions & 0 deletions docs/ru/guide/composition-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Composition API

Чтобы получить доступ к хранилищу в хуке `setup`, можно использовать функцию `useStore`. Это эквивалент получения `this.$store` внутри компонента с помощью Options API.

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

export default {
setup () {
const store = useStore()
}
}
```

## Доступ к состоянию и геттерам

Чтобы получить доступ к состоянию и геттерам, нужно создать `вычисляемые` ref-ссылки для сохранения реактивности. Это эквивалентно созданию вычисляемых свойств с помощью Options API.

```js
import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
setup () {
const store = useStore()

return {
// доступ к состоянию в вычисляемой функции
count: computed(() => store.state.count),

// доступ к геттеру в вычисляемой функции
double: computed(() => store.getters.double)
}
}
}
```

## Доступ к мутациям и действиям

При доступе к мутациям и действиям, можно просто указать методы `commit` и `dispatch` внутри `setup` хука.

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

export default {
setup () {
const store = useStore()

return {
// доступ к мутациям
increment: () => store.commit('increment'),

// доступ к действиям
asyncIncrement: () => store.dispatch('asyncIncrement')
}
}
}
```

## Примеры

Ознакомьтесь с [примером Composition API](https://github.com/vuejs/vuex/tree/4.0/examples/composition) чтобы увидеть пример приложения, использующих Vuex и Vue Composition API.
64 changes: 64 additions & 0 deletions docs/ru/guide/forms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Работа с формами

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/cqKRgEC9" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

При использовании строгого режима Vuex может показаться неочевидным как использовать `v-model` с частью состояния Vuex:

```html
<input v-model="obj.message">
```

Предположим, что `obj` — вычисляемое свойство, которое просто возвращает ссылку на объект из хранилища. В таком случае, `v-model` будет пытаться напрямую изменять значение `obj.message` при действиях пользователя. В строгом режиме такие изменения спровоцируют ошибку, поскольку они происходят вне обработчиков мутаций Vuex.

Для работы с Vuex в такой ситуации, следует привязать значение к `<input>` и отслеживать его изменения по событию `input` или `change`:

```html
<input :value="message" @input="updateMessage">
```

```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` таким образом упускаются. В качестве альтернативы можно предложить использование двунаправленного вычисляемого свойства с сеттером:

```html
<input v-model="message">
```

```js
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
```
122 changes: 122 additions & 0 deletions docs/ru/guide/getters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Геттеры

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/c2Be7TB" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

Иногда может потребоваться вычислять производное состояние на основе состояния хранилища, например, отфильтровать список и затем подсчитать количество элементов:

```js
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
```

Если такие вычисления потребуются более чем в одном компоненте, придётся или дублировать функцию, или выносить её в общий метод, который затем импортировать во всех местах — оба подхода далеки от идеала.

Vuex позволяет определять «геттеры» в хранилище. Можете считать их вычисляемыми свойствами хранилища.

::: warning ВНИМАНИЕ
Начиная с версии Vue 3.0, результат геттера **не кэшируется**, как это делает вычисляемое свойство. Это известная проблема, которая исправлена в релизе Vue 3.1. Больше можно узнать в [PR #1878](https://github.com/vuejs/vuex/pull/1878).
:::

Геттеры получают состояние хранилища первым аргументом:

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

## Стиль обращения как к свойствам

Геттеры доступны в объекте `store.getters`, и можно получить доступ к значениям как свойствам:

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

Вторым аргументом передаются другие геттеры:

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

```js
store.getters.doneTodosCount // -> 1
```

Теперь можно легко использовать его внутри любого компонента:

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

Обратите внимание, что геттеры, доступ к которым выполняется как к свойствам, будут кэшироваться системой реактивности Vue.

## Стиль обращения как к методам

Если возвращать функцию, то появляется возможность также передавать аргументы геттерам. Например, это может пригодиться, когда необходимо возвращать данные по указанному критерию:

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

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

Обратите внимание, что геттеры, доступ к которым выполняется как к методам, будут запускаться каждый раз при их вызове, а результаты не будут кэшироваться.

## Вспомогательная функция `mapGetters`

Функция `mapGetters` просто проксирует геттеры хранилища в локальные вычисляемые свойства компонента:

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

export default {
// ...
computed: {
// смешиваем результат mapGetters с внешним объектом computed
...mapGetters([
'doneTodosCount',
'anotherGetter'
// ...
])
}
}
```

Если необходимо указать другое имя, используйте объектный синтаксис:

```js
...mapGetters({
// проксирует `this.doneCount` в `store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
```
85 changes: 85 additions & 0 deletions docs/ru/guide/hot-reload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Горячая перезагрузка

Vuex поддерживает горячую замену мутаций, модулей, действий и геттеров в момент разработки с помощью [webpack Hot Module Replacement API](https://webpack.js.org/guides/hot-module-replacement/). Аналогичная функциональность в Browserify достижима при использовании плагина [browserify-hmr](https://github.com/AgentME/browserify-hmr/).

Для мутаций и модулей необходимо использовать метод API `store.hotUpdate()`:

```js
// store.js
import { createStore } from 'vuex'
import mutations from './mutations'
import moduleA from './modules/a'

const state = { ... }

const store = createStore({
state,
mutations,
modules: {
a: moduleA
}
})

if (module.hot) {
// отслеживаем действия и мутации как модули для горячей замены
module.hot.accept(['./mutations', './modules/a'], () => {
// запрашиваем обновлённые модули
// (нужно указать .default из-за формата вывода Babel 6)
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) позволяет посмотреть на горячую перезагрузку в реальной жизни.

## Горячая перезагрузка динамических модулей

Если в приложении используются только модули, то можно использовать `require.context` для загрузки и горячей перезагрузки всех модулей динамически.

```js
// store.js
import { createStore } from 'vuex'

// Загружаем все модули
function loadModules() {
const context = require.context('./modules', false, /([a-z_]+)\.js$/i)

const modules = context
.keys()
.map((key) => ({ key, name: key.match(/([a-z_]+)\.js$/i)[1] }))
.reduce(
(modules, { key, name }) => ({
...modules,
[name]: context(key).default
}),
{}
)

return { context, modules }
}

const { context, modules } = loadModules()

const store = createStore({
modules
})

if (module.hot) {
// Горячая перезагрузка при любых изменениях модуля
module.hot.accept(context.id, () => {
const { modules } = loadModules()

store.hotUpdate({
modules
})
})
}
```
66 changes: 66 additions & 0 deletions docs/ru/guide/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Введение

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/cMPa2Uk" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

В центре любого Vuex-приложения находится **хранилище**. «Хранилище» — это контейнер, в котором хранится **состояние** вашего приложения. Два момента отличают хранилище Vuex от простого глобального объекта:

1. Хранилище Vuex реактивно. Когда компоненты Vue полагаются на его состояние, то они будут реактивно и эффективно обновляться, если состояние хранилища изменяется.

2. Нельзя напрямую изменять состояние хранилища. Единственный способ внести изменения — явно **вызвать мутацию**. Это гарантирует, что любое изменение состояния оставляет след и позволяет использовать инструментарий, чтобы лучше понимать ход работы приложения.

## Простейшее хранилище

:::tip ЗАМЕЧАНИЕ
В примерах кода документации используется синтаксис ES2015. Если с ним ещё не знакомы, [сейчас самое время изучить](https://babeljs.io/docs/learn-es2015/)!
:::

После [установки](../installation.md) Vuex, давайте создадим хранилище. Это довольно просто — необходимо указать начальный объект состояния и некоторые мутации:

```js
import { createApp } from 'vue'
import { createStore } from 'vuex'

// Создаём новый экземпляр хранилища
const store = createStore({
state() {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})

const app = createApp({ /* корневой компонент */ })

// Устанавливаем экземпляр хранилища в качестве плагина
app.use(store)
```

Теперь можно получить доступ к объекту состояния через `store.state` и вызвать изменение состояния с помощью метода `store.commit`:

```js
store.commit('increment')

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

В Vue компонентах, можно получить доступ к хранилищу через `this.$store`. Теперь можно совершить мутацию из метода компонента:

```js
methods: {
increment() {
this.$store.commit('increment')
console.log(this.$store.state.count)
}
}
```

Запомните, причина, по которой вызывается мутация вместо изменения `store.state.count` напрямую, в том, что нужно явным образом отслеживать её. Это простое соглашение делает намерение более явным, что упрощает понимание происходящих изменений состояния приложения при чтении кода. Кроме того, это позволяет использовать инструменты для отслеживания каждой мутации, создания снимков состояния или даже использования «машины времени» для отладки.

Использование состояния хранилища в компоненте предполагает просто возврат необходимой части состояния в вычисляемом свойстве, поскольку состояние хранилища реактивно. Инициирование изменений — это просто запуск мутаций в методах компонентов.

Далее разберём все основные понятия детальнее и начнём с [состояния](state.md).
116 changes: 116 additions & 0 deletions docs/ru/guide/migrating-to-4-0-from-3-x.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Миграция на 4.0 с 3.x

Почти весь API Vuex 4 остался неизменным по сравнению с Vuex 3. Тем не менее, есть ещё несколько кардинальных изменении, которые необходимо исправить.

- [Кардинальные изменения](#кардинаnьные-изменения)
- [Процесс установки](#процесс-установки)
- [Поддержка TypeScript](#поддержка-typescript)
- [Сборки соответствуют сборкам Vue 3](#сборки-соответствуют-сборкам-vue-3)
- [Функция `createLogger` экспортируется из основного модуля](#функция-createlogger-экспортируется-из-основного-модуnя)
- [Новые возможности](#новые-возможности)
- [Новая функция композиции «useStore»](#новая-функция-композиции-usestore)

## Кардинальные изменения

### Процесс установки

Чтобы соответствовать новому процессу инициализации Vue 3, процесс установки Vuex изменился. Для создания нового хранилища теперь предлагается использовать функцию `createStore`.

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

export const store = createStore({
state () {
return {
count: 1
}
}
})
```

Чтобы установить Vuex в экземпляр Vue, передайте `store` вместо Vuex.

```js
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'

const app = createApp(App)

app.use(store)

app.mount('#app')
```

:::tip ЗАМЕЧАНИЕ
Хотя технически это не является кардинальным изменением, потому что всё ещё можно использовать и старый синтаксис `new Store(...)`, но рекомендуется этот подход для соответствия с Vue 3 и Vue Router.
:::

### Поддержка TypeScript

Vuex 4 удаляет глобальную типизацию для `this.$store` в компоненте Vue для решения [проблемы #994](https://github.com/vuejs/vuex/issues/994). При использовании с TypeScript, теперь нужно объявить собственный модуль расширения.

Поместите следующий код в проект, чтобы позволить `this.$store` быть корректно типизированным:

```ts
// vuex-shim.d.ts

import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'

declare module '@vue/runtime-core' {
// объявляем собственные состояния хранилища
interface State {
count: number
}

interface ComponentCustomProperties {
$store: Store<State>
}
}
```

Больше можно узнать в разделе [поддержка TypeScript](./typescript-support.md).

### Сборки соответствуют сборкам Vue 3

Следующие сборки сгенерированы для соответствия со сборками Vue 3:

- `vuex.global(.prod).js`
- Для непосредственного использования с `<script src="...">` в браузере. Выставляет глобальный Vuex.
- Глобальная сборка построена как IIFE, а не как UMD, и предназначена только для использования напрямую через `<script src="...">`.
- Содержит подготовленные варианты для production/разработки, кроме того сборка для production предварительно минифицирована. Используйте в production файлы `*.prod.js`.
- `vuex.esm-browser(.prod).js`
- Для использования в нативных импортах ES-модулей (в браузере через `<script type="module">`).
- `vuex.esm-bundler.js`
- Для использования с системами сборки, такими как `webpack`, `rollup` и `parcel`.
- Определение поведения для production/разработки условиями с `process.env.NODE_ENV` (значение будет подменяться системой сборки).
- Нет готовых минифицированных сборок (минификация должна выполняться системой сборки, как и для всего остального кода).
- `vuex.cjs.js`
- Для использования отрисовки на стороне сервера в Node.js через `require()`.

### Функция `createLogger` экспортируется из основного модуля

В Vuex 3, функция `createLogger` экспортировалась из `vuex/dist/logger`, но теперь доступ к ней можно получить из основного пакета `vuex`.

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

## Новые возможности

### Новая функция композиции «useStore»

Vuex 4 представляет новый API с хранилищем при использовании Composition API. Можно использовать функцию композиции `useStore` для извлечения хранилища внутри компонента `setup` хуком.

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

export default {
setup () {
const store = useStore()
}
}
```

Больше можно узнать в разделе [Composition API](./composition-api.md).
341 changes: 341 additions & 0 deletions docs/ru/guide/modules.md

Large diffs are not rendered by default.

159 changes: 159 additions & 0 deletions docs/ru/guide/mutations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Мутации

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/ckMZp4HN" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

Единственным способом изменения состояния хранилища во Vuex являются мутации. Мутации во Vuex очень похожи на события: каждая мутация имеет строковый **тип** и **функцию-обработчик**. В этом обработчике и происходят, собственно, изменения состояния, переданного в функцию первым аргументом:

```js
const store = createStore({
state: {
count: 1
},
mutations: {
increment(state) {
// изменяем состояние
state.count++
}
}
})
```

Вызывать функцию-обработчик напрямую — нельзя. Это больше похоже на обработку события: «Когда мутация типа `increment` инициирована, вызывается этот обработчик». Чтобы инициировать обработку мутации, необходимо вызвать `store.commit`, указав её тип:

```js
store.commit('increment')
```

## Мутации с нагрузкой

При вызове `store.commit` в мутацию можно также передать дополнительный параметр, называемый **нагрузкой (`payload`)**:

```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
})
```

## Объектный синтаксис

Другой способ вызвать мутацию — это передать в commit единственный параметр, в котором `type` указан напрямую:

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

При использовании объектной записи, объект передаётся в качестве нагрузки целиком, так что обработчик остаётся тем же самым:

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

## Использование констант для обозначения типов мутаций

В разных вариантах реализации Flux такой подход применяется весьма часто. Вынесение всех констант с типами мутаций и действий в отдельный файл позволяет упростить использования линтеров и других инструментов, а также чтобы дать другим разработчикам возможность с первого взгляда понять, какие мутации возможны в приложении:

```js
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
```

```js
// store.js
import { createStore } from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = createStore({
state: { ... },
mutations: {
// «вычисляемые имена» из ES2015 позволяют использовать
// константу в качестве имени функции
[SOME_MUTATION] (state) {
// здесь будет изменяться состояние
}
}
})
```

Тем не менее, использовать константы для указания типов мутаций совершенно необязательно, хотя это и может оказаться полезным в крупных проектах.

## Мутации должны быть синхронными

Нужно помнить одно важное правило: **обработчики мутаций обязаны быть синхронными**. Почему? Рассмотрим пример:

```js
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
```

Теперь представьте, что отлаживаете приложение и смотрите в лог мутаций в инструментах разработчика. Для каждой залогированной мутации devtools должен сохранить слепки состояния приложения «до» и «после» её наступления. Однако, асинхронный коллбэк внутри приведённой выше мутации делает это невозможным: мутация-то уже записана, и у devtools нет никакой возможности знать, что далее будет вызван коллбэк, а, значит, и инициируемые им изменения становится, по сути дела, невозможно отследить.

## Вызов мутаций в компонентах

Мутации можно вызывать из кода компонентов, используя `this.$store.commit('xxx')`, или применяя вспомогательный метод `mapMutations`, который проксирует вызовы `store.commit` через методы компонентов (для этого требуется наличие корневой ссылки на хранилище `$store`):

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

export default {
// ...
methods: {
...mapMutations([
'increment', // `this.increment()` будет вызывать `this.$store.commit('increment')`

// mapMutations также поддерживает нагрузку:
'incrementBy' // `this.incrementBy(amount)` будет вызывать `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // `this.add()` будет вызывать `this.$store.commit('increment')`
})
}
}
```

## О действиях

Привнесение асинхронности в мутации могло бы изрядно затруднить понимание логики программы. Например, если вызываются два метода, оба с асинхронными коллбэками, изменяющими состояние приложения — как предсказать, какой из коллбэков будет вызван первым? Именно поэтому концепции изменений и асинхронности рассматриваются по отдельности. Во Vuex **мутации — это синхронные транзакции**:

```js
store.commit('increment')
// все изменения состояния, вызываемые мутацией «increment»,
// к этому моменту уже должны произойти.
```

Для обработки асинхронных операций существуют [действия](actions.md).
137 changes: 137 additions & 0 deletions docs/ru/guide/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Плагины

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/cvp8ZkCR" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

Хранилища Vuex принимают опцию `plugins`, предоставляющую хуки для каждой мутации. Vuex-плагин — это просто функция, получающая хранилище в качестве единственного параметра:

```js
const myPlugin = store => {
// вызывается после инициализации хранилища
store.subscribe((mutation, state) => {
// вызывается после каждой мутации
// мутация передаётся в формате `{ type, payload }`.
})
}
```

Используются плагины так:

```js
const store = createStore({
// ...
plugins: [myPlugin]
})
```

## Вызов мутаций из плагинов

Плагинам не разрешается напрямую изменять состояние приложения — как и компоненты, они могут только вызывать изменения опосредованно, используя мутации.

Вызывая мутации, плагин может синхронизировать источник данных с хранилищем данных в приложении. Например, для синхронизации хранилища с веб-сокетом (пример намеренно упрощён, в реальной ситуации у `createWebSocketPlugin` были бы дополнительные опции):

```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 = createStore({
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 = createStore({
// ...
plugins: process.env.NODE_ENV !== 'production'
? [myPluginWithSnapshot]
: []
})
```

Плагин будет использоваться по умолчанию. В production-окружении понадобится [DefinePlugin](https://webpack.js.org/plugins/define-plugin/) для webpack, или [envify](https://github.com/hughsk/envify) для Browserify. Чтобы изменить выражение `process.env.NODE_ENV !== 'production'` на значение `false` в финальной сборке.

## Встроенный плагин логирования

В комплекте с Vuex идёт плагин логирования, который можно использовать при отладке:

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

const store = createStore({
plugins: [createLogger()]
})
```

Функция `createLogger` принимает следующие опции:

```js
const logger = createLogger({
collapsed: false, // автоматически раскрывать залогированные мутации
filter(mutation, stateBefore, stateAfter) {
// возвращает `true`, если мутация должна быть залогирована
// `mutation` — это объект `{ type, payload }`
return mutation.type !== 'aBlocklistedMutation'
},
actionFilter (action, state) {
// аналогично `filter`, но для действий
// `action` будет объектом `{ type, payload }`
return action.type !== 'aBlocklistedAction'
},
transformer(state) {
// обработать состояние перед логированием
// например, позволяет рассматривать только конкретное поддерево
return state.subTree
},
mutationTransformer(mutation) {
// мутации логируются в формате `{ type, payload }`,
// но это можно изменить
return mutation.type
},
actionTransformer (action) {
// аналогично `mutationTransformer`, но для действий
return action.type
},
logActions: true, // логирование действий
logMutations: true, // логирование мутаций
logger: console // реализация API `console`, по умолчанию `console`
})
```

Логирующий плагин также можно включить напрямую используя отдельный тег `<script>`, помещающий функцию `createVuexLogger` в глобальное пространство имён.

Обратите внимание, что этот плагин делает слепки состояний, поэтому использовать его стоит только на этапе разработки.
99 changes: 99 additions & 0 deletions docs/ru/guide/state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Состояние

## Единое дерево состояния

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/cWw3Zhb" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

Vuex использует **единое дерево состояния** — когда один объект содержит всё глобальное состояние приложения и служит «единственным источником истины». Это также означает, что в приложении будет только одно такое хранилище. Единое дерево состояния позволяет легко найти нужную его часть или делать снимки текущего состояния приложения в целях отладки.

Единое дерево состояния не противоречит модульности — в следующих главах изучим, как можно разделить состояние и мутации на под-модули.

Данные, которые хранятся во Vuex должны следовать тем же правилам, что и `data` в экземпляре Vue, т.е. объект состояния должен быть простым. **См. также:** [Vue#data](https://v3.ru.vuejs.org/ru/api/options-data.html#data).

## Использование состояния Vuex в компонентах Vue

Итак, как использовать состояние хранилища в компонентах Vue? Поскольку хранилище Vuex реактивно, самый простой способ «получения» — просто вернуть часть состояния хранилища в [вычисляемом свойстве](https://v3.ru.vuejs.org/ru/api/options-data.html#computed):

```js
// создадим компонент-счётчик:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count() {
return store.state.count
}
}
}
```

Любые изменения `store.state.count` вызовут перерасчёт вычисляемого свойства и запуск связанных с ним обновлений DOM.

Однако этот подход заставляет компонент полагаться на синглтон глобального хранилища. При использовании модульной системы, это потребует импортировать хранилище в каждом компоненте, который использует его состояние, а также усложнит тестирование компонента.

Vuex «внедряет» хранилище во все дочерние компоненты из корневого компонента через систему плагинов Vue и будет доступно для них как `this.$store`. Давайте обновим реализацию `Counter`:

```js
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count() {
return this.$store.state.count
}
}
}
```

## Вспомогательная функция `mapState`

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/c8Pz7BSK" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

Когда компонент должен использовать множество свойств или геттеров хранилища, объявлять все эти вычисляемые свойства может быть утомительно. В таких случаях можно использовать функцию `mapState`, которая автоматически генерирует вычисляемые свойства:

```js
// В полной сборке функция доступна через Vuex.mapState
import { mapState } from 'vuex'

export default {
// ...
computed: mapState({
// стрелочные функции могут сделать код очень кратким
count: state => state.count,

// передача строки 'count' аналогична записи `state => state.count`
countAlias: 'count',

// для доступа к локальному состоянию через `this`,
// необходимо использовать обычную функцию
countPlusLocalState(state) {
return state.count + this.localCount
}
})
}
```

Можно передавать массив строк в `mapState`, когда имя сопоставляемого вычисляемого свойства совпадает с именем поддерева состояния:

```js
computed: mapState([
// проксирует в this.count доступ к store.state.count
'count'
])
```

## Оператор распространения объектов

Обратите внимание, `mapState` возвращает объект. Как же его использовать в сочетании с другими локальными вычисляемыми свойствами? Для этого обычно приходилось использовать вспомогательные утилиты для объединения нескольких объектов в один, который передавать в `computed`. Однако, с помощью [оператора распространения объектов](https://github.com/tc39/proposal-object-rest-spread) можно значительно упростить синтаксис:

```js
computed: {
localComputed () { /* ... */ },
// смешиваем результат mapState с внешним объектом
...mapState({
// ...
})
}
```

## Компоненты всё ещё могут иметь локальное состояние

Использование Vuex **не означает, что нужно выносить всё состояние** в хранилище. Хотя перемещение большей части состояния во Vuex, сделает мутации более явными и удобными для отладки, это также может привести к многословности и ненужному усложнению логики. Если часть состояния относится только к одному компоненту, лучше оставить его в качестве локального состояния. Необходимо взвесить все компромиссы и принять решение, соответствующее потребностям приложения и вектора его развития.
25 changes: 25 additions & 0 deletions docs/ru/guide/strict.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Строгий режим (strict mode)

Для включения строгого режима просто укажите `strict: true` при создании хранилища Vuex:

```js
const store = createStore({
// ...
strict: true
})
```

В строгом режиме любая попытка внесения изменений в состояние Vuex, кроме мутаций, будет выбрасывать ошибку. Это гарантирует, что все мутации состояния будут явно отслеживаться через инструменты отладки.

## Разработка vs Production

**Не используйте строгий режим в production!** Строгий режим запускает глубокое отслеживание за деревом состояния приложения в синхронном режиме для обнаружения несоответствующих мутаций, и это может быть затратным для производительности, когда совершается большое количество мутаций. Убедитесь, что выключили этот режим в production чтобы избежать ухудшения производительности.

Аналогично плагинам, при использовании инструментов сборки это можно сделать так:

```js
const store = createStore({
// ...
strict: process.env.NODE_ENV !== 'production'
})
```
32 changes: 32 additions & 0 deletions docs/ru/guide/structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Структура приложения

В действительности Vuex не накладывает каких-то значительных ограничений на используемую структуру кода. Однако, он требует соблюдения нескольких высокоуровневых принципов:

1. Глобальное состояние приложения должно содержаться в глобальном хранилище;

2. Единственным механизмом изменения этого состояния являются **мутации**, являющиеся синхронными транзакциями;

3. Асинхронные операции инкапсулируются в **действия** или их комбинации.

Пока следуем этим правилам, можно использовать любую структуру проекта. Если файл хранилища становится слишком большим, просто начните выносить действия, мутации и геттеры в отдельные файлы.

Для любого нетривиального приложения скорее всего понадобится использование модулей. Вот пример возможной структуры проекта:

```bash
├── index.html
├── main.js
├── api
│   └── ... # абстракции для выполнения запросов к API
├── components
│   ├── App.vue
│   └── ...
└── store
├── index.js # здесь собираем модули и экспортируем хранилище
├── actions.js # корневые действия
├── mutations.js # корневые мутации
└── modules
   ├── cart.js # модуль корзины
   └── products.js # модуль товаров
```

Для справки можно использовать [пример корзины покупок](https://github.com/vuejs/vuex/tree/main/examples/classic/shopping-cart).
256 changes: 256 additions & 0 deletions docs/ru/guide/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
# Тестирование

<div class="scrimba"><a href="https://scrimba.com/p/pnyzgAP/cPGkpJhq" target="_blank" rel="noopener noreferrer">Пройдите этот урок на Scrimba</a></div>

В основном предметом модульного тестирования во Vuex являются мутации и действия.

### Тестирование мутаций

Мутации тестировать довольно просто, так как они представляют из себя всего лишь простые функции, поведение которых полностью зависит от переданных параметров. Один трюк заключается в том, что если использовать модули ES2015 и помещать мутации в файле `store.js`, то помимо экспорта по умолчанию, необходимо экспортировать мутации с помощью именованного экспорта:

```js
const state = { ... }

// именованный экспорт `mutations` отдельно от самого хранилища
export const mutations = { ... }

export default createStore({
state,
mutations
})
```

Пример тестирования мутаций с использованием Mocha + Chai (хотя можно использовать и любые другие библиотеки):

```js
// mutations.js
export const mutations = {
increment: state => state.count++
}
```

```js
// mutations.spec.js
import { expect } from 'chai'
import { mutations } from './store'

// деструктурирующее присваивание из `mutations`
const { increment } = mutations

describe('mutations', () => {
it('INCREMENT', () => {
// фиксируем состояние
const state = { count: 0 }
// применяем мутацию
increment(state)
// оцениваем результат
expect(state.count).to.equal(1)
})
})
```

### Тестирование действий

Действия тестировать несколько сложнее, поскольку они могут обращаться к внешним API. При тестировании действий обычно приходится заниматься подделкой внешних объектов — например, вызовы к API можно вынести в отдельный сервис, и в рамках тестов этот сервис подменить поддельным. Для упрощения имитации зависимостей можно использовать webpack и [inject-loader](https://github.com/plasticine/inject-loader) для сборки файлов тестов.

Пример тестирования асинхронного действия:

```js
// actions.js
import shop from '../api/shop'

export const getAllProducts = ({ commit }) => {
commit('REQUEST_PRODUCTS')
shop.getProducts(products => {
commit('RECEIVE_PRODUCTS', products)
})
}
```

```js
// actions.spec.js

// для inline-загрузчиков используйте синтаксис require
// и inject-loader, возвращающий фабрику модулей, помогающую
// подменять зависимости
import { expect } from 'chai'
const actionsInjector = require('inject-loader!./actions')

// создаём поддельную зависимость
const actions = actionsInjector({
'../api/shop': {
getProducts(cb) {
setTimeout(() => {
cb([/* поддельный ответ от сервера */])
}, 100)
}
}
})

// вспомогательная функция для тестирования действия, которое должно вызывать известные мутации
const testAction = (action, payload, state, expectedMutations, done) => {
let count = 0

// поддельная функция вызова мутаций
const commit = (type, payload) => {
const mutation = expectedMutations[count]

try {
expect(type).to.equal(mutation.type)
expect(payload).to.deep.equal(mutation.payload)
} catch (error) {
done(error)
}

count++
if (count >= expectedMutations.length) {
done()
}
}

// вызываем действие с поддельным хранилищем и аргументами
action({ commit, state }, payload)

// проверяем, были ли инициированы мутации
if (expectedMutations.length === 0) {
expect(count).to.equal(0)
done()
}
}

describe('actions', () => {
it('getAllProducts', done => {
testAction(
actions.getAllProducts,
null,
{},
[
{ type: 'REQUEST_PRODUCTS' },
{
type: 'RECEIVE_PRODUCTS',
payload: {
/* поддельный ответ */
}
}
],
done
)
})
})
```

Если есть шпионы (spies), доступные в тестовой среде (например, через [Sinon.JS](http://sinonjs.org/)), можно использовать их вместо вспомогательной функции `testAction`:

```js
describe('actions', () => {
it('getAllProducts', () => {
const commit = sinon.spy()
const state = {}

actions.getAllProducts({ commit, state })

expect(commit.args).to.deep.equal([
['REQUEST_PRODUCTS'],
[
'RECEIVE_PRODUCTS',
{
/* поддельный ответ */
}
]
])
})
})
```

### Тестирование геттеров

Геттеры, занимающиеся сложными вычислениями, тоже неплохо бы тестировать. Как и с мутациями, тут всё просто:

Пример тестирования геттера:

```js
// getters.js
export const getters = {
filteredProducts(state, { filterCategory }) {
return state.products.filter(product => {
return product.category === filterCategory
})
}
}
```

```js
// getters.spec.js
import { expect } from 'chai'
import { getters } from './getters'

describe('getters', () => {
it('filteredProducts', () => {
// поддельное состояние
const state = {
products: [
{ id: 1, title: 'Apple', category: 'fruit' },
{ id: 2, title: 'Orange', category: 'fruit' },
{ id: 3, title: 'Carrot', category: 'vegetable' }
]
}
// поддельный параметр геттера
const filterCategory = 'fruit'

// получаем результат выполнения тестируемого геттера
const result = getters.filteredProducts(state, { filterCategory })

// проверяем результат
expect(result).to.deep.equal([
{ id: 1, title: 'Apple', category: 'fruit' },
{ id: 2, title: 'Orange', category: 'fruit' }
])
})
})
```

### Запуск тестов

Если должным образом соблюдаете правила написания мутаций и действий, результирующие тесты не должны зависеть от API браузера. Поэтому их можно просто собрать webpack'ом и запустить в Node. С другой стороны, можно использовать `mocha-loader` или Karma + `karma-webpack`, и запускать тесты в реальных браузерах.

#### Запуск в Node

Используйте следующую конфигурацию webpack (в сочетании с соответствующим [`.babelrc`](https://babeljs.io/docs/usage/babelrc/)):

```js
// webpack.config.js
module.exports = {
entry: './test.js',
output: {
path: __dirname,
filename: 'test-bundle.js'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
}
}
```

Затем в терминале:

```bash
webpack
mocha test-bundle.js
```

### Запуск в браузерах

1. Установите `mocha-loader`
2. Измените `entry` в приведённой выше конфигурации Webpack на `'mocha-loader!babel-loader!./test.js'`.
3. Запустите `webpack-dev-server`, используя эту конфигурацию
4. Откройте в браузере `localhost:8080/webpack-dev-server/test-bundle`.

### Запуск в браузерах при помощи Karma и karma-webpack

Обратитесь к [документации vue-loader](https://vue-loader.vuejs.org/ru/workflow/testing.html).
133 changes: 133 additions & 0 deletions docs/ru/guide/typescript-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Поддержка TypeScript

Vuex предоставляет типизацию, поэтому можно использовать TypeScript при создании хранилища. Специальная конфигурация не требуется. Пожалуйста, следуйте [настройке TypeScript в Vue](https://v3.ru.vuejs.org/ru/guide/typescript-support.html), чтобы настроить проект.

Однако, если писать компоненты Vue на TypeScript, то нужно выполнить несколько шагов, которые требуются для правильного вывода типов хранилища.

## Типизация `$store` свойства в Vue компоненте

Vuex не предоставляет типизацию для свойства `this.$store` из коробки. При использовании с TypeScript, необходимо объявить собственный модуль расширения.

Для этого объявляем пользовательскую типизацию для Vue `ComponentCustomProperties`, добавив файл деклараций в каталог вашего проекта:

```ts
// vuex.d.ts
import { Store } from 'vuex'

declare module '@vue/runtime-core' {
// объявляем собственные состояния хранилища
interface State {
count: number
}

// указываем типизацию для `this.$store`
interface ComponentCustomProperties {
$store: Store<State>
}
}
```

## Типизация функции композиции `useStore`

При создании компонента Vue в стиле Composition API, скорее всего захочется, чтобы `useStore` возвращал типизированное хранилище. Для этого необходимо:

1. Определить типизированный `InjectionKey`.
2. Указать типизированный `InjectionKey` при установке хранилища в приложение Vue.
3. Передать типизированный `InjectionKey` методу `useStore`.

Давайте разбираться с этим шаг за шагом. Сначала определяем ключ используя Vue интерфейс `InjectionKey` вместе с собственным определением типизации хранилища:

```ts
// store.ts
import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'

// определяем типизацию для состояния хранилища
export interface State {
count: number
}

// определяем ключ внедрения
export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
state: {
count: 0
}
})
```

Затем передаём определённый ключ внедрения при установке хранилища в Vue приложение:

```ts
// main.ts
import { createApp } from 'vue'
import { store, key } from './store'

const app = createApp({ ... })

// передаём ключ внедрения
app.use(store, key)

app.mount('#app')
```

Теперь можно передать ключ методу `useStore` для получения типизированного хранилища.

```ts
// в vue компоненте
import { useStore } from 'vuex'
import { key } from './store'

export default {
setup () {
const store = useStore(key)

store.state.count // типизировано как число
}
}
```

Под капотом Vuex устанавливает хранилище в приложение Vue, используя функции Vue [Provide/Inject](https://v3.ru.vuejs.org/ru/api/composition-api.html#provide-inject) поэтому ключ внедрения важный фактор.

### Упрощение использования `useStore`

Необходимость импортировать `InjectionKey` и передавать его везде, где используется `useStore` может быстро превратиться в повторяющуюся задачу. Чтобы упростить задачу, определяем собственную функцию композиции для получения типизированного хранилища:

```ts
// store.ts
import { InjectionKey } from 'vue'
import { createStore, useStore as baseUseStore, Store } from 'vuex'

export interface State {
count: number
}

export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
state: {
count: 0
}
})

// определяем собственную функцию композиции `useStore`
export function useStore () {
return baseUseStore(key)
}
```

Теперь, импортировав собственную функцию композиции, можно получить типизированное хранилище **без необходимости** предоставления ключа внедрения и его типизации:

```ts
// в vue компоненте
import { useStore } from './store'

export default {
setup () {
const store = useStore()

store.state.count // типизировано как число
}
}
```
76 changes: 76 additions & 0 deletions docs/ru/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Что такое Vuex?

:::tip Pinia - новый стандарт по-умолчанию

Официальная библиотека управления состоянием для Vue была изменена на [Pinia](https://pinia.vuejs.org). Pinia имеет почти такой же или улучшенный API что и Vuex 5, описанный в [Vuex 5 RFC](https://github.com/vuejs/rfcs/pull/271). Можно рассматривать Pinia как Vuex 5 с новым именем. Pinia также можно использовать вместе с Vue 2.x.

Vuex 3 and 4 по-прежнему будут поддерживаться. Вряд ли к ним добавятся новые функции. Vuex и Pinia могут быть установлены в одном проекте. Если переносите существующее приложение Vuex на Pinia, это может быть подходящим вариантом. Если планируете начать новый проект, настоятельно рекомендуется вместо этого использовать Pinia.
:::

<VideoPreview />

Vuex — **паттерн управления состоянием + библиотека** для приложений на Vue.js. Он служит централизованным хранилищем данных для всех компонентов приложения с правилами, гарантирующими, что состояние может быть изменено только предсказуемым образом.

## Что такое «паттерн управления состоянием»?

Давайте начнём с простого приложения Vue, реализующего счётчик:

```js
const Counter = {
// состояние
data() {
return {
count: 0,
}
},
// представление
template: `
<div>{{ count }}</div>
`,
// действия
methods: {
increment() {
this.count++
}
}
}

createApp(Counter).mount('#app')
```

Это самостоятельное приложение состоит из следующих частей:

- **Состояние** — «источник истины», управляющий приложением;
- **Представление** — отображение **состояния** заданное декларативно;
- **Действия** — возможные пути изменения состояния приложения в ответ на взаимодействие пользователя с **представлением**.

Вот простейшее представление концепции «однонаправленного потока данных»:

<p style="text-align: center; margin: 2em">
<img style="width:100%;max-width:450px;" src="/ru/flow.png">
</p>

Простота быстро исчезает, когда у нас появляется **несколько компонентов, основывающихся на одном и том же состоянии**:

- Несколько представлений могут зависеть от одной и той же части состояния приложения.
- Действия из разных представлений могут оказывать влияние на одни и те же части состояния приложения.

Решая первую проблему, придётся передавать одни и те же данные входными параметрами в глубоко вложенные компоненты. Это часто сложно и утомительно, а для соседних компонентов такое и вовсе не сработает. Решая вторую проблему, можно прийти к таким решениям, как обращение по ссылкам к родительским/дочерним экземплярам или попыткам изменять и синхронизировать несколько копий состояния через события. Оба подхода хрупки и быстро приводят к появлению кода, который невозможно поддерживать.

Так почему бы не вынести всё общее состояние приложения из компонентов и управлять им в глобальном синглтоне? При этом наше дерево компонентов становится одним большим «представлением», и любой компонент может получить доступ к состоянию приложения или вызывать действия для изменения состояния, независимо от того, где они находятся в дереве!

Чётко определяя и разделяя концепции, возникающие при управлении состоянием, и требуя соблюдения определённых правил, которые поддерживают независимость между представлениями и состояниями, мы лучше структурируем код и облегчаем его поддержку.

Это основная идея Vuex, вдохновлённого [Flux](https://facebook.github.io/flux/docs/overview), [Redux](http://redux.js.org/) и [Архитектурой Elm](https://guide.elm-lang.org/architecture/). В отличие от других паттернов, Vuex реализован в виде библиотеки, специально предназначенной для Vue.js, чтобы использовать его систему реактивности для эффективного обновления.

Если хотите изучить Vuex в интерактивном режиме, попробуйте этот [курс по Vuex на Scrimba.](https://scrimba.com/g/gvuex)

![vuex](/ru/vuex.png)

## Когда следует использовать Vuex?

Vuex помогает управлять совместно используемым состоянием ценой привнесения новых концепций и вспомогательного кода. Компромисс, когда кратковременная продуктивность страдает на благо долгосрочной.

Если ещё не приходилось создавать крупные SPA и лишь знакомитесь с Vuex, это может показаться многословным и сложным. Всё в порядке — простые приложения могут легко обходиться и без Vuex. Возможно, будет достаточно простого паттерна [глобальной шины событий](https://v3.ru.vuejs.org/ru/guide/state-management.html#создание-простого-контеинера-состояния-с-нуля). Но если создаёте SPA среднего или крупного размера, то, скорее всего, уже сталкивались с ситуациями, которые заставляли задуматься о том, как лучше управлять состоянием вне компонентов Vue, а Vuex в таком случае может стать вполне естественным следующим шагом. Есть хорошая цитата от Дэна Абрамова, автора Redux:

> Flux-библиотеки похожи на очки: вы будете точно знать, когда они вам понадобятся.
41 changes: 41 additions & 0 deletions docs/ru/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Установка

## Скачивание напрямую / CDN

[https://unpkg.com/vuex@4](https://unpkg.com/vuex@4)

<!--email_off-->

[Unpkg.com](https://unpkg.com) предоставляет CDN-ссылки для NPM-пакетов. Ссылка, приведённая выше, всегда указывает на самый последней релиз Vuex, доступный в NPM. Кроме того, можно указывать в ссылке конкретную версию или тег, например `https://unpkg.com/vuex@4.0.0/dist/vuex.global.js`.

<!--/email_off-->

Подключите `vuex` после Vue, и установка произойдёт автоматически:

```html
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>
```

## NPM

```bash
npm install vuex@next --save
```

## Yarn

```bash
yarn add vuex@next --save
```

## Версия для разработки

Для использования самой новой dev-сборки `vuex` — склонируйте репозиторий с GitHub вручную и запустите сборку:

```bash
git clone https://github.com/vuejs/vuex.git node_modules/vuex
cd node_modules/vuex
yarn
yarn build
```