You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/ru/README.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -7,7 +7,7 @@
7
7
- vue-router 2.5.0+
8
8
- vue-loader 12.0.0+ & vue-style-loader 3.0.0+
9
9
10
-
Если вы ранее использовали Vue 2.2 с серверным рендерингом, вы заметите, что рекомендуемая структура кода теперь [немного отличается](./guide/structure.md) (с новой опцией [runInNewContext](./api/README.md#runinnewcontext), установленной в `false`). Ваше существующее приложение по-прежнему будет работать, но лучше внесите изменения с учётом новых рекомендаций.
10
+
Если вы ранее использовали Vue 2.2 с серверным рендерингом, вы заметите, что рекомендуемая структура кода теперь [немного отличается](./guide/structure.md) (с новой опцией [runInNewContext](./api/#runinnewcontext), установленной в `false`). Ваше существующее приложение по-прежнему будет работать, но лучше внесите изменения с учётом новых рекомендаций.
- `string | (() => string |Promise<string>)` (с версии 2.6)
90
+
91
+
**При использовании строкового шаблона:**
92
+
87
93
Предоставляет шаблон для всей HTML-страницы. Шаблон должен содержать комментарий `<!--vue-ssr-outlet-->`, который определяет место подстановки отрендеренного контента приложения.
88
94
89
95
Шаблон также поддерживает базовые интерполяции с использованием контекста рендера:
С версии 2.5.0+, встраиваемый скрипт также автоматически удаляется в режиме production.
103
109
110
+
С версии 2.6.0+, если присутствует `context.nonce`, он будет добавлен как атрибут `nonce` во встраиваемый скрипт. Это позволит встраиваемому скрипту соответствовать CSP, который требует nonce.
111
+
104
112
Кроме того, когда предоставлен `clientManifest`, шаблон автоматически внедряет следующее:
105
113
106
114
- JavaScript и CSS ресурсы для клиентской части, необходимые для рендеринга (с асинхронными фрагментами добавляемыми автоматически);
107
115
- Оптимальные ресурсы `<link rel="preload/prefetch">` для отображаемой страницы.
108
116
109
117
Вы можете отключить все автоматические внедрения передав `inject:false` в рендерер.
110
118
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
+
constrenderer=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).
- [Внедрение ресурсов вручную](../guide/build-config.md#внедрение-ресурсов-вручную)
115
151
116
152
### clientManifest
117
153
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).
119
155
120
156
### inject
121
157
122
158
Контролирует, выполнять ли автоматические внедрения при использовании `template`. По умолчанию `true`.
123
159
124
-
См. также: [Внедрение ресурсов вручную](../guide/build-config.md#manual-asset-injection).
160
+
См. также: [Внедрение ресурсов вручную](../guide/build-config.md#внедрение-ресурсов-вручную).
- Возможные значения: `boolean |'once'` (`'once'` поддерживается только с версии 2.3.1+)
168
204
169
-
По умолчанию, рендерер сборки будет создавать новый контекст V8 для каждого рендеринга и повторно исполнять всю сборку. Это имеет некоторые преимущества — например, код приложения изолирован от процесса сервера и не нужно беспокоиться [о проблеме «синглтона с состоянием»](../guide/structure.md#avoid-stateful-singletons), которая упоминалась ранее в руководстве. Однако этот режим требует значительных затрат производительности, поскольку повторное выполнение сборки обходится дорого, особенно когда приложение становится большим.
205
+
По умолчанию, рендерер сборки будет создавать новый контекст V8 для каждого рендеринга и повторно исполнять всю сборку. Это имеет некоторые преимущества — например, код приложения изолирован от процесса сервера и не нужно беспокоиться [о проблеме «синглтона с состоянием»](../guide/structure.md#избегайте-сингnтонов-с-состоянием), которая упоминалась ранее в руководстве. Однако этот режим требует значительных затрат производительности, поскольку повторное выполнение сборки обходится дорого, особенно когда приложение становится большим.
170
206
171
207
По умолчанию эта опция имеет значение `true` для обеспечения обратной совместимости, но рекомендуется использовать `runInNewContext:false` или `runInNewContext:'once'` всегда, когда это возможно.
Указание пути базового каталога для серверной сборки для разрешения зависимостей из `node_modules` в нём. Это необходимо только в том случае, если сгенерированный файл сборки располагается в другом месте, в отличии от используемых NPM-зависимостей, или когда ваш `vue-server-renderer`подключен NPM-ссылкой в вашем текущем проекте.
224
+
Указание пути базового каталога для серверной сборки для разрешения зависимостей из `node_modules` в нём. Это необходимо только в том случае, если сгенерированный файл сборки располагается в другом месте, в отличии от используемых NPM-зависимостей, или когда ваш `vue-server-renderer`подключён NPM-ссылкой в вашем текущем проекте.
189
225
190
226
### cache
191
227
192
-
Реализация [кэширования на уровне компонентов](../guide/caching.md#component-level-caching). Объект кэша должен реализовать следующий интерфейс (соответствуя нотациям Flow):
228
+
Реализация [кэширования на уровне компонентов](../guide/caching.md#кэширование-на-уровне-компонентов). Объект кэша должен реализовать следующий интерфейс (соответствуя нотациям Flow):
Например, можете посмотреть [серверную реализацию для директивы `v-show`](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js).
247
283
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
+
248
310
## Плагины webpack
249
311
250
312
Webpack плагины предоставляются как отдельные файлы, которые должны быть подключены напрямую:
Конфигурация клиентской части может оставаться практически такой же, как и базовой. Очевидно, вам нужно указать `entry` на файл входной точки клиентской части. Кроме того, если вы используете `CommonsChunkPlugin`, убедитесь, что используете его только в конфигурации клиентской части, потому что для серверной сборки требуется одна точка входа.
Copy file name to clipboardExpand all lines: docs/ru/guide/caching.md
+4Lines changed: 4 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -73,6 +73,10 @@ export default {
73
73
74
74
При возвращении константы компонент всегда будет кэшироваться, что отлично подходит для чисто статических компонентов.
75
75
76
+
::: tip Исключение из кэширования
77
+
С версии 2.6.0 можно явно возвращать `false` в `serverCacheKey`, чтобы не использовать компонент из кэша, а отрисовывать заново.
78
+
:::
79
+
76
80
### Когда использовать кэширование компонентов
77
81
78
82
Если рендерер найдёт попадание в кэше для компонента во время рендеринга, он будет напрямую переиспользовать кэшированный результат для всего поддерева. Это означает, что вы **НЕ ДОЛЖНЫ** кэшировать компонент когда:
Во время серверного рендеринга, мы собственно отображаем «снимок» нашего приложения, поэтому если приложение использует какие-то асинхронные данные **они должны быть предварительно загружены и разрешены до начала процесса рендеринга**.
5
+
Во время серверного рендеринга, мы отображаем «снимок» нашего приложения. Асинхронные данные из наших компонентов должны быть доступны до того, как мы смонтируем приложение на стороне клиента — в противном случае клиентское приложение будет отображено с использованием другого состояния, а гидратация завершится ошибкой.
6
6
7
-
Другая проблема заключается в том, что на клиенте эти же данные должны быть доступны перед моментом монтирования приложения на клиенте — иначе клиентское приложение будет отображено с использованием другого состояния и гидратация не будет выполнена.
8
-
9
-
Чтобы решить эту проблему, полученные данные должны находиться вне компонентов представления, в специальном хранилище данных или в «контейнере состояния». На сервере мы можем предзагрузить и заполнить данные в хранилище перед рендерингом. Кроме того, мы будем сериализовывать и встраивать состояние в HTML. Хранилище на клиентской стороне сможет непосредственно получать вложенное состояние перед монтированием приложения.
7
+
Для решения этой проблемы, полученные данные должны находиться вне компонентов представления, в специальном хранилище данных или в «контейнере состояния». На сервере мы можем предзагрузить и заполнить данные в хранилище перед рендерингом. Кроме того, мы будем сериализовывать и встраивать состояние в HTML после успешного рендеринга приложения. Хранилище на клиентской стороне сможет непосредственно получать вложенное состояние перед монтированием приложения.
10
8
11
9
Для этой цели мы будем использовать официальную библиотеку управления состоянием — [Vuex](https://github.com/vuejs/vuex/). Давайте создадим файл `store.js`, с некоторой симуляцией логики получения элемента на основе id:
12
10
@@ -23,18 +21,22 @@ import { fetchItem } from './api'
23
21
24
22
exportfunctioncreateStore () {
25
23
returnnewVuex.Store({
26
-
state: {
24
+
// ВАЖНО: состояние должно быть функцией,
25
+
// чтобы модуль мог инстанцироваться несколько раз
26
+
state: () => ({
27
27
items: {}
28
-
},
28
+
}),
29
+
29
30
actions: {
30
31
fetchItem ({ commit }, id) {
31
32
// возвращаем Promise через `store.dispatch()`
32
-
// чтобы мы могли понять когда данные будут загружены
33
+
// чтобы могли определять когда данные будут загружены
33
34
returnfetchItem(id).then(item=> {
34
35
commit('setItem', { id, item })
35
36
})
36
37
}
37
38
},
39
+
38
40
mutations: {
39
41
setItem (state, { id, item }) {
40
42
Vue.set(state.items, id, item)
@@ -44,6 +46,11 @@ export function createStore () {
44
46
}
45
47
```
46
48
49
+
::: warning ВНИМАНИЕ
50
+
Большую часть времени вы должны оборачивать `state` в функцию, чтобы она не вызвала утечек памяти при следующих запусках на стороне сервера.
@@ -78,36 +85,70 @@ export function createApp () {
78
85
79
86
Итак, где мы должны размещать код, который вызывает действия по предзагрузке данных?
80
87
81
-
Данные, которые нам нужно получить, определяются посещённым маршрутом — что также определяет какие компоненты должны будут отображены. Фактически, данные необходимые для данного маршрута, также являются данными, необходимыми компонентам, отображаемым для этого маршрута. Поэтому будет логичным разместить логику получения данных внутри компонентов маршрута.
88
+
Данные, которые нам нужно получить, определяются посещённым маршрутом — что также определяет какие компоненты должны будут отображены. Фактически, данные необходимые для данного маршрута, также требуются компонентам, отображаемым для этого маршрута. Поэтому будет логичным разместить логику получения данных внутри компонентов маршрута.
82
89
83
-
Мы предоставим пользовательскую статичную функцию `asyncData` в наших компонентах маршрута. Обратите внимание, так как эта функция будет вызываться до инициализации компонентов, у неё не будет доступа к `this`. Информация хранилища и маршрута должна передаваться аргументами:
90
+
Мы будем использовать опцию `serverPrefetch` (добавлена в версии 2.6.0+) в компонентах. Эта опция распознаётся рендерингом на стороне сервера и приостанавливает отрисовку до тех пор, пока Promise не разрешится. Это позволяет нам «дожидаться» асинхронных данных в процессе отрисовки.
91
+
92
+
::: tip Совет
93
+
Можно использовать `serverPrefetch` в любом компоненте, а не только в компонентах указываемых в маршрутах.
94
+
:::
95
+
96
+
Вот пример компонента `Item.vue`, который отображается по маршруту `'/item/:id'`. Поскольку экземпляр компонента уже создан на этом этапе, он имеет доступ к `this`:
Необходимо проверять рендерился ли компонент на стороне сервера в хуке `mounted` во избежание выполнения логики загрузки дважды.
143
+
:::
144
+
145
+
::: tip Совет
146
+
Можно увидеть одинаковую логику `fetchItem()`, повторяющуюся несколько раз (в коллбэках `serverPrefetch`, `mounted` и `watch`) в каждом компоненте — рекомендуется создать собственную абстракцию (например, примесь или плагин) для упрощения подобного кода.
147
+
:::
109
148
110
-
В `entry-server.js` мы можем получить компоненты, соответствующие маршруту, с помощью `router.getMatchedComponents()`, и вызвать `asyncData` если компонент предоставляет её. Затем нужно присоединить разрешённое состояние к контексту рендера.
149
+
## Инъекция финального состояния
150
+
151
+
Теперь мы знаем что процесс отрисовки будет дожидаться получения данных в наших компонентах, но как же узнавать когда всё «готово»? Для этого потребуется использовать коллбэк `rendered` в контексте рендера (также добавлено в версии 2.6), который будет вызывать серверный рендер после завершения всего процесса рендеринга. В этот момент хранилище должно быть заполнено данными своего финального состояния. Затем мы можем внедрить его в контекст в этом коллбэке:
// Инициализируем состояние хранилища данными, внедрёнными на сервере
159
191
store.replaceState(window.__INITIAL_STATE__)
160
192
}
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()`, чтобы все асинхронные компоненты были разрешены.
// здесь мы должны вызвать индикатор загрузки, если используем его
199
-
200
-
Promise.all(activated.map(c=> {
201
-
if (c.asyncData) {
202
-
returnc.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')
255
195
```
256
196
257
197
## Разделение кода хранилища
@@ -262,21 +202,24 @@ Vue.mixin({
262
202
// store/modules/foo.js
263
203
exportdefault {
264
204
namespaced:true,
205
+
265
206
// ВАЖНО: state должен быть функцией, чтобы
266
207
// модуль мог инстанцироваться несколько раз
267
208
state: () => ({
268
209
count:0
269
210
}),
211
+
270
212
actions: {
271
213
inc: ({ commit }) =>commit('inc')
272
214
},
215
+
273
216
mutations: {
274
217
inc:state=>state.count++
275
218
}
276
219
}
277
220
```
278
221
279
-
Мы можем использовать `store.registerModule` для ленивой регистрации этого модуля в хуке `asyncData` компонента маршрута:
222
+
Мы можем использовать `store.registerModule` для ленивой регистрации этого модуля в хуке `serverPrefetch` компонента маршрута:
280
223
281
224
```html
282
225
// внутри компонента маршрута
@@ -289,9 +232,30 @@ export default {
289
232
importfooStoreModulefrom'../store/modules/foo'
290
233
291
234
exportdefault {
292
-
asyncData ({ store }) {
293
-
store.registerModule('foo', fooStoreModule)
294
-
returnstore.dispatch('foo/inc')
235
+
computed: {
236
+
fooCount () {
237
+
returnthis.$store.state.foo.count
238
+
}
239
+
},
240
+
241
+
// Только на стороне сервера
242
+
serverPrefetch () {
243
+
this.registerFoo()
244
+
returnthis.fooInc()
245
+
},
246
+
247
+
// Только на стороне клиента
248
+
mounted () {
249
+
// Мы уже увеличили значение 'count' на сервере
250
+
// Определяем это, проверив что 'foo' уже существует
251
+
constalreadyIncremented=!!this.$store.state.foo
252
+
253
+
// Регистрируем модуль foo
254
+
this.registerFoo()
255
+
256
+
if (!alreadyIncremented) {
257
+
this.fooInc()
258
+
}
295
259
},
296
260
297
261
// ВАЖНО: избегайте дублирования регистрации модуля на клиенте
@@ -300,9 +264,14 @@ export default {
300
264
this.$store.unregisterModule('foo')
301
265
},
302
266
303
-
computed: {
304
-
fooCount () {
305
-
returnthis.$store.state.foo.count
267
+
methods: {
268
+
registerFoo () {
269
+
// Сохраняем предыдущее состояние, если оно внедрялось на стороне сервера
Поскольку модуль теперь является зависимостью компонента маршрута, он будет перемещён в асинхронный фрагмент компонента маршрута с помощью Webpack.
313
282
314
-
---
315
-
316
-
Фух, это было много кода! Это связано с тем, что универсальная загрузка данных является, вероятно, самой сложной проблемой в приложении с рендерингом на стороне сервера, и таким образом мы закладываем хороший фундамент для облегчения дальнейшей разработки. После создания такой заготовки, создание отдельных компонентов будет приятным занятием.
283
+
::: warning ВНИМАНИЕ
284
+
Не забывайте использовать опцию `preserveState: true` для `registerModule` чтобы сохранять состояние, внедрённое сервером.
0 commit comments