Перейти к содержанию

Гидратация

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

Что такое гидратация

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

Почему гидратация важна?

Гидратация улучшает производительность приложения, поскольку позволяет избежать лишней работы по повторному созданию узлов DOM. Вместо этого Angular пытается сопоставить существующие элементы DOM со структурой приложения во время выполнения и повторно использует узлы DOM, когда это возможно. Это приводит к повышению производительности, которую можно измерить с помощью статистики Core Web Vitals (CWV), такой как снижение задержки первого ввода (FID) и Largest Contentful Paint (LCP), а также Cumulative Layout Shift (CLS). Улучшение этих показателей также влияет на такие вещи, как производительность SEO.

Без включенной гидратации рендеринг приложений Angular на стороне сервера будет разрушать и заново отображать DOM приложения, что может привести к заметному мерцанию пользовательского интерфейса. Этот повторный рендеринг может негативно повлиять на Core Web Vitals, как LCP, и вызвать смещение макета. Включение гидратации позволяет повторно использовать существующий DOM и предотвращает мерцание.

Как включить гидратацию в Angular

Прежде чем начать работу с гидратацией, вы должны иметь приложение с рендерингом на стороне сервера (SSR). Следуйте Angular Universal Guide, чтобы сначала включить рендеринг на стороне сервера. Как только SSR заработает в вашем приложении, вы можете включить гидратацию, посетив ваш главный компонент или модуль приложения и импортировав provideClientHydration из @angular/platform-browser. Затем вы добавите этот провайдер в список загрузочных провайдеров вашего приложения.

1
2
3
4
5
6
7
8
9
import {
    bootstrapApplication,
    provideClientHydration,
} from '@angular/platform-browser';
/* ... */

bootstrapApplication(RootCmp, {
    providers: [provideClientHydration()],
});

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { provideClientHydration } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

@NgModule({
    declarations: [RootCmp],
    exports: [RootCmp],
    bootstrap: [RootCmp],
    providers: [provideClientHydration()],
})
export class AppModule {}

Важное замечание: убедитесь, что вызов provideClientHydration() также включен в набор провайдеров, который используется для загрузки приложения на сервере. В приложениях со структурой проекта по умолчанию (генерируется командой ng new), достаточно добавить вызов корневого AppModule, поскольку этот модуль импортируется серверным модулем. Если вы используете пользовательскую установку, добавьте вызов provideClientHydration() в список провайдеров в конфигурации bootstrap сервера.

После того как вы выполнили эти шаги и запустили свой сервер, загрузите свое приложение в браузере.

Скорее всего, вам придется исправить экземпляры Direct DOM Manipulation, прежде чем гидратация заработает в полную силу, либо перейдя на конструкции Angular, либо используя ngSkipHydration. Смотрите Constraints, Direct DOM Manipulation, и How to skip hydration for particular components для более подробной информации.

Запустив приложение в режиме dev, вы можете убедиться, что гидратация включена, открыв Developer Tools в браузере и просмотрев консоль. Вы должны увидеть сообщение, содержащее статистику, связанную с гидратацией, например, количество гидратированных компонентов и узлов. Примечание: Angular рассчитывает статистику на основе всех компонентов, отображаемых на странице, включая те, которые получены из сторонних библиотек.

Ограничения

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

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

Если существует несоответствие между структурами дерева DOM сервера и клиента, процесс гидратации будет сталкиваться с проблемами, пытаясь сопоставить то, что ожидалось, с тем, что действительно присутствует в DOM. Компоненты, которые выполняют прямые манипуляции с DOM, используя собственные API DOM, являются наиболее распространенным виновником.

Прямая манипуляция DOM

Если у вас есть компоненты, которые манипулируют DOM с помощью собственных DOM API или используют innerHTML или outerHTML, процесс гидратации столкнется с ошибками. Конкретные случаи, когда манипулирование DOM является проблемой, это такие ситуации, как доступ к document, запрос определенных элементов и введение дополнительных узлов с помощью appendChild. Отсоединение узлов DOM и перемещение их в другие места также приведет к ошибкам.

Это происходит потому, что Angular не знает об этих изменениях DOM и не может разрешить их в процессе гидратации. Angular ожидает определенную структуру, но при попытке гидратации он столкнется с другой структурой. Это несоответствие приведет к неудаче гидратации и ошибке DOM mismatch (см. ниже).

Лучше всего рефакторить ваш компонент, чтобы избежать такого рода манипуляций с DOM. Постарайтесь использовать API Angular для выполнения этой работы, если это возможно. Если вы не можете рефакторить это поведение, используйте атрибут ngSkipHydration (описано ниже), пока не сможете рефакторить его до дружественного к гидратации решения.

Валидная структура HTML

Есть несколько случаев, когда шаблон компонента не имеет корректной HTML-структуры, что может привести к ошибке несоответствия DOM при гидратации.

В качестве примера, вот некоторые из наиболее распространенных случаев этой проблемы.

  • <table> без <tbody>
  • <div> внутри <p>
  • <a> внутри <h1>
  • <a> внутри другого <a>

Если вы не уверены в правильности HTML, вы можете использовать syntax validator для его проверки.

Конфигурация сохранения белых пространств

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

Убедитесь, что этот параметр установлен последовательно в tsconfig.server.json для вашего сервера и tsconfig.app.json для ваших сборок браузера. Несоответствующее значение приведет к нарушению гидратации.

Если вы решите установить этот параметр в вашем tsconfig, мы рекомендуем устанавливать его только в tsconfig.app.json, от которого по умолчанию наследуется tsconfig.server.json.

Пользовательские или Noop Zone.js пока не поддерживаются.

Гидратация полагается на сигнал от Zone.js, когда он становится стабильным внутри приложения, чтобы Angular мог запустить процесс сериализации на сервере или очистку после гидратации на клиенте для удаления узлов DOM, которые остались невостребованными.

Предоставление пользовательской или "noop" реализации Zone.js может привести к другому времени наступления события "stable", что приведет к запуску сериализации или очистки слишком рано или слишком поздно. Это еще не полностью поддерживаемая конфигурация, и вам может понадобиться настроить время события onStable в пользовательской реализации Zone.js.

Ошибки

Существует несколько ошибок, связанных с гидратацией, с которыми вы можете столкнуться, начиная от несоответствия узлов и заканчивая случаями, когда ngSkipHydration был использован на недопустимом узле. Наиболее распространенная ошибка, которая может возникнуть, связана с прямым манипулированием DOM с помощью родных API, в результате чего hydration не может найти или сопоставить ожидаемую структуру дерева DOM на клиенте с той, которая была отображена на сервере. Другой случай, когда вы можете столкнуться с этим типом ошибки, был упомянут в предыдущем разделе о валидных структурах HTML. Поэтому убедитесь, что HTML в ваших шаблонах использует валидную структуру, и вы избежите этой ошибки.

Для получения полной справки об ошибках, связанных с гидратацией, посетите Справочник ошибок.

Как пропустить гидратацию для определенных компонентов

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

1
<example-cmp ngSkipHydration />

Также вы можете установить ngSkipHydration в качестве привязки к хосту.

1
2
3
4
5
@Component({
    /* ... */
    host: { ngSkipHydration: 'true' },
})
class ExampleCmp {}

Атрибут ngSkipHydration заставит Angular пропустить гидратацию всего компонента и его дочерних элементов. Использование этого атрибута означает, что компонент будет вести себя так, как будто гидратация не включена, то есть он будет разрушаться и заново отображаться.

Это исправит проблемы рендеринга, но это означает, что для этого компонента (и его дочерних компонентов) вы не получите преимуществ гидратации. Вам нужно будет скорректировать реализацию вашего компонента, чтобы избежать шаблонов, нарушающих гидратацию (т.е. прямого манипулирования DOM), чтобы иметь возможность удалить аннотацию пропуска гидратации.

Атрибут ngSkipHydration может быть использован только на узлах компонентов. Angular выдаст ошибку, если этот атрибут будет добавлен к другим узлам.

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

I18N

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

Сторонние библиотеки с манипуляцией DOM

Существует ряд сторонних библиотек, рендеринг которых зависит от манипуляций с DOM. Ярким примером является D3 charts. Эти библиотеки работали без гидратации, но они могут вызывать ошибки несоответствия DOM, когда гидратация включена. На данный момент, если вы столкнулись с ошибками несоответствия DOM при использовании одной из этих библиотек, вы можете добавить атрибут ngSkipHydration к компоненту, который рендерит с помощью этой библиотеки.

Комментарии