Skip to content

Commit eb1c9db

Browse files
Alex-Sokolovkazupon
authored andcommittedMar 25, 2019
docs: [RU] Translation update (#237)
* [RU] Translation moved to VuePress * add redirects * hydration.md fix * docs: [RU] typos * docs: [RU] 2.6 updates * docs: [RU] typo
1 parent 260c32c commit eb1c9db

File tree

5 files changed

+184
-149
lines changed

5 files changed

+184
-149
lines changed
 

‎docs/ru/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- vue-router 2.5.0+
88
- vue-loader 12.0.0+ & vue-style-loader 3.0.0+
99

10-
Если вы ранее использовали Vue 2.2 с серверным рендерингом, вы заметите, что рекомендуемая структура кода теперь [немного отличается](./guide/structure.md) (с новой опцией [runInNewContext](./api/README.md#runinnewcontext), установленной в `false`). Ваше существующее приложение по-прежнему будет работать, но лучше внесите изменения с учётом новых рекомендаций.
10+
Если вы ранее использовали Vue 2.2 с серверным рендерингом, вы заметите, что рекомендуемая структура кода теперь [немного отличается](./guide/structure.md) (с новой опцией [runInNewContext](./api/#runinnewcontext), установленной в `false`). Ваше существующее приложение по-прежнему будет работать, но лучше внесите изменения с учётом новых рекомендаций.
1111
:::
1212

1313
## Что такое серверный рендеринг (SSR)?

‎docs/ru/api/README.md

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ bundleRenderer.renderToStream([context]): stream.Readable
8484
8585
### template
8686
87+
- **Тип:**
88+
- `string`
89+
- `string | (() => string | Promise<string>)` (с версии 2.6)
90+
91+
**При использовании строкового шаблона:**
92+
8793
Предоставляет шаблон для всей HTML-страницы. Шаблон должен содержать комментарий `<!--vue-ssr-outlet-->`, который определяет место подстановки отрендеренного контента приложения.
8894
8995
Шаблон также поддерживает базовые интерполяции с использованием контекста рендера:
@@ -101,27 +107,57 @@ bundleRenderer.renderToStream([context]): stream.Readable
101107
102108
С версии 2.5.0+, встраиваемый скрипт также автоматически удаляется в режиме production.
103109
110+
С версии 2.6.0+, если присутствует `context.nonce`, он будет добавлен как атрибут `nonce` во встраиваемый скрипт. Это позволит встраиваемому скрипту соответствовать CSP, который требует nonce.
111+
104112
Кроме того, когда предоставлен `clientManifest`, шаблон автоматически внедряет следующее:
105113
106114
- JavaScript и CSS ресурсы для клиентской части, необходимые для рендеринга (с асинхронными фрагментами добавляемыми автоматически);
107115
- Оптимальные ресурсы `<link rel="preload/prefetch">` для отображаемой страницы.
108116
109117
Вы можете отключить все автоматические внедрения передав `inject: false` в рендерер.
110118
119+
**При использовании функции шаблона:**
120+
121+
::: warning ВНИМАНИЕ
122+
Шаблоны в виде функции поддерживаются только с версии 2.6+ и при использовании `renderer.renderToString`. Это НЕ ПОДДЕРЖИВАЕТСЯ в `renderer.renderToStream`.
123+
:::
124+
125+
Опция `template` также может быть функцией, которая возвращает отрендеренный HTML или Promise, который разрешится отрендеренным HTML. Это позволит использовать собственные строковые шаблоны JavaScript и потенциальные асинхронные операции в процессе рендеринга шаблона.
126+
127+
Функция принимает два аргумента:
128+
129+
1. Результат рендеринга компонента приложения в виде строки;
130+
2. Объект контекста рендеринга.
131+
132+
Пример:
133+
134+
``` js
135+
const renderer = createRenderer({
136+
template: (result, context) => {
137+
return `<html>
138+
<head>${context.head}</head>
139+
<body>${result}</body>
140+
<html>`
141+
}
142+
})
143+
```
144+
145+
Обратите внимание, что при использовании собственной функции для шаблона ничего автоматически не будет добавляться — вы полностью контролируете то, что будет включаться в HTML, но также нести ответственность за включение всего необходимого (например, ссылки на ресурсы, если вы используете bundle renderer).
146+
111147
См. также:
112148
113-
- [Использование шаблона страниц](../guide/#using-a-page-template)
114-
- [Внедрение ресурсов вручную](../guide/build-config.md#manual-asset-injection)
149+
- [Использование шаблона страниц](../guide/#испоnьзование-шабnона-страниц)
150+
- [Внедрение ресурсов вручную](../guide/build-config.md#внедрение-ресурсов-вручную)
115151
116152
### clientManifest
117153
118-
Предоставляет объект манифеста клиентской сборки, сгенерированный `vue-server-renderer/client-plugin`. Клиентский манифест предоставляет для рендерера сборки необходимую информацию для автоматического внедрения ресурсов в шаблон HTML. Подробнее в разделе [Генерация `clientManifest`](../guide/build-config.md#generating-clientmanifest).
154+
Предоставляет объект манифеста клиентской сборки, сгенерированный `vue-server-renderer/client-plugin`. Клиентский манифест предоставляет для рендерера сборки необходимую информацию для автоматического внедрения ресурсов в шаблон HTML. Подробнее в разделе [Генерация `clientManifest`](../guide/build-config.md#генерация-clientmanifest).
119155
120156
### inject
121157
122158
Контролирует, выполнять ли автоматические внедрения при использовании `template`. По умолчанию `true`.
123159
124-
См. также: [Внедрение ресурсов вручную](../guide/build-config.md#manual-asset-injection).
160+
См. также: [Внедрение ресурсов вручную](../guide/build-config.md#внедрение-ресурсов-вручную).
125161
126162
### shouldPreload
127163
@@ -166,7 +202,7 @@ const renderer = createBundleRenderer(bundle, {
166202
- Используется только в `createBundleRenderer`
167203
- Возможные значения: `boolean | 'once'` (`'once'` поддерживается только с версии 2.3.1+)
168204
169-
По умолчанию, рендерер сборки будет создавать новый контекст V8 для каждого рендеринга и повторно исполнять всю сборку. Это имеет некоторые преимущества — например, код приложения изолирован от процесса сервера и не нужно беспокоиться [о проблеме «синглтона с состоянием»](../guide/structure.md#avoid-stateful-singletons), которая упоминалась ранее в руководстве. Однако этот режим требует значительных затрат производительности, поскольку повторное выполнение сборки обходится дорого, особенно когда приложение становится большим.
205+
По умолчанию, рендерер сборки будет создавать новый контекст V8 для каждого рендеринга и повторно исполнять всю сборку. Это имеет некоторые преимущества — например, код приложения изолирован от процесса сервера и не нужно беспокоиться [о проблеме «синглтона с состоянием»](../guide/structure.md#избегайте-сингnтонов-с-состоянием), которая упоминалась ранее в руководстве. Однако этот режим требует значительных затрат производительности, поскольку повторное выполнение сборки обходится дорого, особенно когда приложение становится большим.
170206
171207
По умолчанию эта опция имеет значение `true` для обеспечения обратной совместимости, но рекомендуется использовать `runInNewContext: false` или `runInNewContext: 'once'` всегда, когда это возможно.
172208
@@ -185,11 +221,11 @@ const renderer = createBundleRenderer(bundle, {
185221
186222
- Используется только в `createBundleRenderer`
187223
188-
Указание пути базового каталога для серверной сборки для разрешения зависимостей из `node_modules` в нём. Это необходимо только в том случае, если сгенерированный файл сборки располагается в другом месте, в отличии от используемых NPM-зависимостей, или когда ваш `vue-server-renderer` подключен NPM-ссылкой в вашем текущем проекте.
224+
Указание пути базового каталога для серверной сборки для разрешения зависимостей из `node_modules` в нём. Это необходимо только в том случае, если сгенерированный файл сборки располагается в другом месте, в отличии от используемых NPM-зависимостей, или когда ваш `vue-server-renderer` подключён NPM-ссылкой в вашем текущем проекте.
189225
190226
### cache
191227
192-
Реализация [кэширования на уровне компонентов](../guide/caching.md#component-level-caching). Объект кэша должен реализовать следующий интерфейс (соответствуя нотациям Flow):
228+
Реализация [кэширования на уровне компонентов](../guide/caching.md#кэширование-на-уровне-компонентов). Объект кэша должен реализовать следующий интерфейс (соответствуя нотациям Flow):
193229
194230
``` js
195231
type RenderCache = {
@@ -245,6 +281,32 @@ const renderer = createRenderer({
245281
246282
Например, можете посмотреть [серверную реализацию для директивы `v-show`](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js).
247283
284+
### serializer
285+
286+
> Добавлено в версии 2.6
287+
288+
Пользовательская функция сериализатора для `context.state`. Поскольку сериализованное состояние будет частью вашего итогового HTML, важно использовать функцию, которая должным образом экранирует символы HTML по соображениям безопасности. Сериализатор по умолчанию — [serialize-javascript](https://github.com/yahoo/serialize-javascript) с опцией `{ isJSON: true }`.
289+
290+
## Опции компонента только для сервера
291+
292+
### serverCacheKey
293+
294+
- **Тип:** `(props) => any`
295+
296+
Возвращает ключ кэша для компонента на основе входных параметров. НЕ ИМЕЕТ доступа к `this`.
297+
298+
Начиная с версии 2.6, вы можете явно отказываться от кэширования, возвращая значение `false`.
299+
300+
Подробнее в [Кэшировании на уровне компонентов](../guide/caching.html#кэширование-на-уровне-компонентов).
301+
302+
### serverPrefetch
303+
304+
- **Тип:** `() => Promise<any>`
305+
306+
Загрузка асинхронных данных во время рендеринга на стороне сервера. Он должен сохранить полученные данные в глобальном хранилище или вернуть Promise. Рендерер сервера будет дожидаться разрешения Promise в этом хуке. Этот хук имеет доступ к экземпляру компонента через `this`.
307+
308+
Подробнее в [Предзагрузке данных и состояния](../guide/data.html).
309+
248310
## Плагины webpack
249311
250312
Webpack плагины предоставляются как отдельные файлы, которые должны быть подключены напрямую:

‎docs/ru/guide/build-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', {
7070

7171
Конфигурация клиентской части может оставаться практически такой же, как и базовой. Очевидно, вам нужно указать `entry` на файл входной точки клиентской части. Кроме того, если вы используете `CommonsChunkPlugin`, убедитесь, что используете его только в конфигурации клиентской части, потому что для серверной сборки требуется одна точка входа.
7272

73-
### Generating `clientManifest`
73+
### Генерация `clientManifest`
7474

7575
> требуется версия 2.3.0+
7676

‎docs/ru/guide/caching.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export default {
7373

7474
При возвращении константы компонент всегда будет кэшироваться, что отлично подходит для чисто статических компонентов.
7575

76+
::: tip Исключение из кэширования
77+
С версии 2.6.0 можно явно возвращать `false` в `serverCacheKey`, чтобы не использовать компонент из кэша, а отрисовывать заново.
78+
:::
79+
7680
### Когда использовать кэширование компонентов
7781

7882
Если рендерер найдёт попадание в кэше для компонента во время рендеринга, он будет напрямую переиспользовать кэшированный результат для всего поддерева. Это означает, что вы **НЕ ДОЛЖНЫ** кэшировать компонент когда:

‎docs/ru/guide/data.md

Lines changed: 109 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
## Хранение данных
44

5-
Во время серверного рендеринга, мы собственно отображаем «снимок» нашего приложения, поэтому если приложение использует какие-то асинхронные данные **они должны быть предварительно загружены и разрешены до начала процесса рендеринга**.
5+
Во время серверного рендеринга, мы отображаем «снимок» нашего приложения. Асинхронные данные из наших компонентов должны быть доступны до того, как мы смонтируем приложение на стороне клиента — в противном случае клиентское приложение будет отображено с использованием другого состояния, а гидратация завершится ошибкой.
66

7-
Другая проблема заключается в том, что на клиенте эти же данные должны быть доступны перед моментом монтирования приложения на клиенте — иначе клиентское приложение будет отображено с использованием другого состояния и гидратация не будет выполнена.
8-
9-
Чтобы решить эту проблему, полученные данные должны находиться вне компонентов представления, в специальном хранилище данных или в «контейнере состояния». На сервере мы можем предзагрузить и заполнить данные в хранилище перед рендерингом. Кроме того, мы будем сериализовывать и встраивать состояние в HTML. Хранилище на клиентской стороне сможет непосредственно получать вложенное состояние перед монтированием приложения.
7+
Для решения этой проблемы, полученные данные должны находиться вне компонентов представления, в специальном хранилище данных или в «контейнере состояния». На сервере мы можем предзагрузить и заполнить данные в хранилище перед рендерингом. Кроме того, мы будем сериализовывать и встраивать состояние в HTML после успешного рендеринга приложения. Хранилище на клиентской стороне сможет непосредственно получать вложенное состояние перед монтированием приложения.
108

119
Для этой цели мы будем использовать официальную библиотеку управления состоянием — [Vuex](https://github.com/vuejs/vuex/). Давайте создадим файл `store.js`, с некоторой симуляцией логики получения элемента на основе id:
1210

@@ -23,18 +21,22 @@ import { fetchItem } from './api'
2321

2422
export function createStore () {
2523
return new Vuex.Store({
26-
state: {
24+
// ВАЖНО: состояние должно быть функцией,
25+
// чтобы модуль мог инстанцироваться несколько раз
26+
state: () => ({
2727
items: {}
28-
},
28+
}),
29+
2930
actions: {
3031
fetchItem ({ commit }, id) {
3132
// возвращаем Promise через `store.dispatch()`
32-
// чтобы мы могли понять когда данные будут загружены
33+
// чтобы могли определять когда данные будут загружены
3334
return fetchItem(id).then(item => {
3435
commit('setItem', { id, item })
3536
})
3637
}
3738
},
39+
3840
mutations: {
3941
setItem (state, { id, item }) {
4042
Vue.set(state.items, id, item)
@@ -44,6 +46,11 @@ export function createStore () {
4446
}
4547
```
4648

49+
::: warning ВНИМАНИЕ
50+
Большую часть времени вы должны оборачивать `state` в функцию, чтобы она не вызвала утечек памяти при следующих запусках на стороне сервера.
51+
[Подробнее](./structure.md#избегайте-сингnтонов-с-состоянием)
52+
:::
53+
4754
И обновляем `app.js`:
4855

4956
``` js
@@ -78,36 +85,70 @@ export function createApp () {
7885

7986
Итак, где мы должны размещать код, который вызывает действия по предзагрузке данных?
8087

81-
Данные, которые нам нужно получить, определяются посещённым маршрутом — что также определяет какие компоненты должны будут отображены. Фактически, данные необходимые для данного маршрута, также являются данными, необходимыми компонентам, отображаемым для этого маршрута. Поэтому будет логичным разместить логику получения данных внутри компонентов маршрута.
88+
Данные, которые нам нужно получить, определяются посещённым маршрутом — что также определяет какие компоненты должны будут отображены. Фактически, данные необходимые для данного маршрута, также требуются компонентам, отображаемым для этого маршрута. Поэтому будет логичным разместить логику получения данных внутри компонентов маршрута.
8289

83-
Мы предоставим пользовательскую статичную функцию `asyncData` в наших компонентах маршрута. Обратите внимание, так как эта функция будет вызываться до инициализации компонентов, у неё не будет доступа к `this`. Информация хранилища и маршрута должна передаваться аргументами:
90+
Мы будем использовать опцию `serverPrefetch` (добавлена в версии 2.6.0+) в компонентах. Эта опция распознаётся рендерингом на стороне сервера и приостанавливает отрисовку до тех пор, пока Promise не разрешится. Это позволяет нам «дожидаться» асинхронных данных в процессе отрисовки.
91+
92+
::: tip Совет
93+
Можно использовать `serverPrefetch` в любом компоненте, а не только в компонентах указываемых в маршрутах.
94+
:::
95+
96+
Вот пример компонента `Item.vue`, который отображается по маршруту `'/item/:id'`. Поскольку экземпляр компонента уже создан на этом этапе, он имеет доступ к `this`:
8497

8598
``` html
8699
<!-- Item.vue -->
87100
<template>
88-
<div>{{ item.title }}</div>
101+
<div v-if="item">{{ item.title }}</div>
102+
<div v-else>...</div>
89103
</template>
90104

91105
<script>
92106
export default {
93-
asyncData ({ store, route }) {
94-
// возвращаем Promise из действия
95-
return store.dispatch('fetchItem', route.params.id)
96-
},
97-
98107
computed: {
99-
// отображаем элемент из состояния хранилища.
108+
// Отображаем элемент из состояния хранилища
100109
item () {
101110
return this.$store.state.items[this.$route.params.id]
102111
}
112+
},
113+
114+
// Только на стороне сервера
115+
// Будет автоматически вызвано рендером сервера
116+
serverPrefetch () {
117+
// возвращает Promise из действия, поэтому
118+
// компонент ждёт данные перед рендерингом
119+
return this.fetchItem()
120+
},
121+
122+
// Только на стороне клиента
123+
mounted () {
124+
// Если мы не сделали ещё это на сервере,
125+
// то получаем элемент (сначала показав текст загрузки)
126+
if (!this.item) {
127+
this.fetchItem()
128+
}
129+
},
130+
131+
methods: {
132+
fetchItem () {
133+
// возвращаем Promise из действия
134+
return this.$store.dispatch('fetchItem', this.$route.params.id)
135+
}
103136
}
104137
}
105138
</script>
106139
```
107140

108-
## Загрузка данных на сервере
141+
::: warning ВНИМАНИЕ
142+
Необходимо проверять рендерился ли компонент на стороне сервера в хуке `mounted` во избежание выполнения логики загрузки дважды.
143+
:::
144+
145+
::: tip Совет
146+
Можно увидеть одинаковую логику `fetchItem()`, повторяющуюся несколько раз (в коллбэках `serverPrefetch`, `mounted` и `watch`) в каждом компоненте — рекомендуется создать собственную абстракцию (например, примесь или плагин) для упрощения подобного кода.
147+
:::
109148

110-
В `entry-server.js` мы можем получить компоненты, соответствующие маршруту, с помощью `router.getMatchedComponents()`, и вызвать `asyncData` если компонент предоставляет её. Затем нужно присоединить разрешённое состояние к контексту рендера.
149+
## Инъекция финального состояния
150+
151+
Теперь мы знаем что процесс отрисовки будет дожидаться получения данных в наших компонентах, но как же узнавать когда всё «готово»? Для этого потребуется использовать коллбэк `rendered` в контексте рендера (также добавлено в версии 2.6), который будет вызывать серверный рендер после завершения всего процесса рендеринга. В этот момент хранилище должно быть заполнено данными своего финального состояния. Затем мы можем внедрить его в контекст в этом коллбэке:
111152

112153
``` js
113154
// entry-server.js
@@ -120,29 +161,17 @@ export default context => {
120161
router.push(context.url)
121162

122163
router.onReady(() => {
123-
const matchedComponents = router.getMatchedComponents()
124-
if (!matchedComponents.length) {
125-
return reject({ code: 404 })
126-
}
127-
128-
// вызов `asyncData()` на всех соответствующих компонентах
129-
Promise.all(matchedComponents.map(Component => {
130-
if (Component.asyncData) {
131-
return Component.asyncData({
132-
store,
133-
route: router.currentRoute
134-
})
135-
}
136-
})).then(() => {
137-
// После разрешения всех preFetch хуков, наше хранилище теперь
138-
// заполнено состоянием, необходимым для рендеринга приложения.
164+
// Хук `rendered` будет вызван, когда приложение завершит рендеринг
165+
context.rendered = () => {
166+
// После рендеринга приложение, наше хранилище теперь
167+
// заполнено финальным состоянием из наших компонентов.
139168
// Когда мы присоединяем состояние к контексту, и есть опция `template`
140169
// используемая для рендерера, состояние будет автоматически
141170
// сериализовано и внедрено в HTML как `window.__INITIAL_STATE__`.
142171
context.state = store.state
172+
}
143173

144-
resolve(app)
145-
}).catch(reject)
174+
resolve(app)
146175
}, reject)
147176
})
148177
}
@@ -153,105 +182,16 @@ export default context => {
153182
``` js
154183
// entry-client.js
155184

156-
const { app, router, store } = createApp()
185+
import { createApp } from './app'
186+
187+
const { app, store } = createApp()
157188

158189
if (window.__INITIAL_STATE__) {
190+
// Инициализируем состояние хранилища данными, внедрёнными на сервере
159191
store.replaceState(window.__INITIAL_STATE__)
160192
}
161-
```
162-
163-
## Загрузка данных на клиенте
164-
165-
На клиенте существует два разных подхода к получению данных:
166-
167-
1. **Разрешить данные перед навигацией по маршруту:**
168-
169-
По этой стратегии приложение остаётся на текущем представлении до тех пор, пока данные необходимые для нового представления не будут загружены и разрешены. Преимущество заключается в том, что новое представление может уже рендерить полный контент, так как всё готово, но если загрузка данных занимает много времени пользователь будет ощущать «застревание» на текущей странице. Поэтому рекомендуется использовать индикатор загрузки данных при использовании этой стратегии.
170-
171-
Мы можем реализовать эту стратегию на клиенте, проверяя соответствующие компоненты и вызывая их функцию `asyncData` внутри глобальных хуков маршрута. Обратите внимание, что мы должны зарегистрировать этот хук после готовности исходного маршрута, чтобы мы снова не забирали данные, полученные с сервера.
172-
173-
``` js
174-
// entry-client.js
175-
176-
// ...опустим лишний код
177-
178-
router.onReady(() => {
179-
// Добавляем хук маршрута для обработки asyncData.
180-
// Выполняем его после разрешения первоначального маршрута,
181-
// чтобы дважды не загружать данные, которые у нас уже есть.
182-
// Используем `router.beforeResolve()`, чтобы все асинхронные компоненты были разрешены.
183-
router.beforeResolve((to, from, next) => {
184-
const matched = router.getMatchedComponents(to)
185-
const prevMatched = router.getMatchedComponents(from)
186-
187-
// мы заботимся только об отсутствующих ранее компонентах,
188-
// поэтому мы сравниваем два списка, пока не найдём отличия
189-
let diffed = false
190-
const activated = matched.filter((c, i) => {
191-
return diffed || (diffed = (prevMatched[i] !== c))
192-
})
193-
194-
if (!activated.length) {
195-
return next()
196-
}
197193

198-
// здесь мы должны вызвать индикатор загрузки, если используем его
199-
200-
Promise.all(activated.map(c => {
201-
if (c.asyncData) {
202-
return c.asyncData({ store, route: to })
203-
}
204-
})).then(() => {
205-
206-
// останавливаем индикатор загрузки
207-
208-
next()
209-
}).catch(next)
210-
})
211-
212-
app.$mount('#app')
213-
})
214-
```
215-
216-
2. **Загружать данные после отображения нового представления:**
217-
218-
Эта стратегия располагает логику загрузки данных на стороне клиента в функции компонента `beforeMount`. Это позволяет переключаться мгновенно при срабатывании навигации по маршруту, поэтому приложение ощущается более отзывчивым. Однако на момент отображения нового представления у него не будет полных данных. Поэтому необходимо добавлять условие проверки загруженности состояния для каждого компонента, использующего эту стратегию.
219-
220-
Этого можно достичь с помощью глобальной примеси на клиенте:
221-
222-
``` js
223-
Vue.mixin({
224-
beforeMount () {
225-
const { asyncData } = this.$options
226-
if (asyncData) {
227-
// присваиваем операцию загрузки к Promise
228-
// чтобы в компонентах мы могли делать так `this.dataPromise.then(...)`
229-
// для выполнения других задач после готовности данных
230-
this.dataPromise = asyncData({
231-
store: this.$store,
232-
route: this.$route
233-
})
234-
}
235-
}
236-
})
237-
```
238-
239-
Эти две стратегии в конечном счёте являются различными решениями UX и должны выбираться на основе фактического сценария разрабатываемого приложения. Но, независимо от выбранной вами стратегии, функция `asyncData` также должна вызываться при повторном использовании компонента маршрута (тот же маршрут, но параметры изменились, например с `user/1` на `user/2`). Мы также можем обрабатывать это с помощью глобальной примеси для клиентской части:
240-
241-
``` js
242-
Vue.mixin({
243-
beforeRouteUpdate (to, from, next) {
244-
const { asyncData } = this.$options
245-
if (asyncData) {
246-
asyncData({
247-
store: this.$store,
248-
route: to
249-
}).then(next).catch(next)
250-
} else {
251-
next()
252-
}
253-
}
254-
})
194+
app.$mount('#app')
255195
```
256196

257197
## Разделение кода хранилища
@@ -262,21 +202,24 @@ Vue.mixin({
262202
// store/modules/foo.js
263203
export default {
264204
namespaced: true,
205+
265206
// ВАЖНО: state должен быть функцией, чтобы
266207
// модуль мог инстанцироваться несколько раз
267208
state: () => ({
268209
count: 0
269210
}),
211+
270212
actions: {
271213
inc: ({ commit }) => commit('inc')
272214
},
215+
273216
mutations: {
274217
inc: state => state.count++
275218
}
276219
}
277220
```
278221

279-
Мы можем использовать `store.registerModule` для ленивой регистрации этого модуля в хуке `asyncData` компонента маршрута:
222+
Мы можем использовать `store.registerModule` для ленивой регистрации этого модуля в хуке `serverPrefetch` компонента маршрута:
280223

281224
``` html
282225
// внутри компонента маршрута
@@ -289,9 +232,30 @@ export default {
289232
import fooStoreModule from '../store/modules/foo'
290233
291234
export default {
292-
asyncData ({ store }) {
293-
store.registerModule('foo', fooStoreModule)
294-
return store.dispatch('foo/inc')
235+
computed: {
236+
fooCount () {
237+
return this.$store.state.foo.count
238+
}
239+
},
240+
241+
// Только на стороне сервера
242+
serverPrefetch () {
243+
this.registerFoo()
244+
return this.fooInc()
245+
},
246+
247+
// Только на стороне клиента
248+
mounted () {
249+
// Мы уже увеличили значение 'count' на сервере
250+
// Определяем это, проверив что 'foo' уже существует
251+
const alreadyIncremented = !!this.$store.state.foo
252+
253+
// Регистрируем модуль foo
254+
this.registerFoo()
255+
256+
if (!alreadyIncremented) {
257+
this.fooInc()
258+
}
295259
},
296260
297261
// ВАЖНО: избегайте дублирования регистрации модуля на клиенте
@@ -300,9 +264,14 @@ export default {
300264
this.$store.unregisterModule('foo')
301265
},
302266
303-
computed: {
304-
fooCount () {
305-
return this.$store.state.foo.count
267+
methods: {
268+
registerFoo () {
269+
// Сохраняем предыдущее состояние, если оно внедрялось на стороне сервера
270+
this.$store.registerModule('foo', fooStoreModule, { preserveState: true })
271+
},
272+
273+
fooInc () {
274+
return this.$store.dispatch('foo/inc')
306275
}
307276
}
308277
}
@@ -311,6 +280,6 @@ export default {
311280

312281
Поскольку модуль теперь является зависимостью компонента маршрута, он будет перемещён в асинхронный фрагмент компонента маршрута с помощью Webpack.
313282

314-
---
315-
316-
Фух, это было много кода! Это связано с тем, что универсальная загрузка данных является, вероятно, самой сложной проблемой в приложении с рендерингом на стороне сервера, и таким образом мы закладываем хороший фундамент для облегчения дальнейшей разработки. После создания такой заготовки, создание отдельных компонентов будет приятным занятием.
283+
::: warning ВНИМАНИЕ
284+
Не забывайте использовать опцию `preserveState: true` для `registerModule` чтобы сохранять состояние, внедрённое сервером.
285+
:::

0 commit comments

Comments
 (0)
Please sign in to comment.