Написание универсального кода

Прежде чем идти дальше, давайте немного обсудим ограничения при написании «универсального» кода — кода, который выполняется как на сервере, так и на клиенте. Из-за различий в API платформы, поведение нашего кода будет отличаться при работе в разных средах выполнения. Здесь мы рассмотрим ключевые моменты, которые вам нужно знать.

Реактивность данных на сервере

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

Поскольку фактический процесс рендеринга должен быть детерминированным, мы также будем «предзагружать» данные на сервере — это означает, что состояние приложения будет разрешённым, на момент начала рендеринга. А это означает, что на сервере реактивность данных не нужна, поэтому по умолчанию она отключена. Отключение реактивности данных также позволяет избежать уменьшения производительности из-за отсутствия необходимости преобразования данных в реактивные объекты.

Хуки жизненного цикла компонента

Так как динамических обновлений нет, из всех хуков жизненного цикла будут вызваны только beforeCreate и created во время серверного рендеринга (SSR). Это означает, что код внутри любых других хуков жизненного цикла, таких как beforeMount или mounted, будет выполняться только на клиенте.

Стоит ещё отметить, что вам следует избегать кода, который производит глобальные побочные эффекты (side effects) в хуках beforeCreate и created, например устанавливая таймеры с помощью setInterval. В коде на стороне клиента мы можем установить таймер, а затем остановить его в beforeDestroy или destroyed. Но, поскольку хуки уничтожения не будут вызываться во время SSR, таймеры останутся навсегда. Чтобы избежать этого, переместите такой код в beforeMount или mounted.

Доступ к специфичному API платформы

Универсальный код не может использовать API специализированное для какой-то конкретной платформы (platform-specific APIs), потому что если ваш код будет использовать глобальные переменные браузеров window или document, то возникнут ошибки при выполнении в Node.js, и наоборот.

Для задач, разделяемых между сервером и клиентом, но использующих разные API платформы, рекомендуется создавать обёртки платформо-специфичных реализаций в универсальное API, или использовать библиотеки, которые делают это за вас. Например, axios (opens new window) — это HTTP-клиент предоставляющий одинаковое API как для сервера так и для клиента.

Для API только для браузеров общий подход — ленивый (lazy) доступ к ним, внутри хуков жизненного цикла только для клиентской стороны.

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

Пользовательские директивы

Большинство пользовательских директив непосредственно манипулируют DOM, поэтому будут вызывать ошибки во время SSR. Существует два способа обойти это:

  1. Предпочитайте использовать компоненты в качестве механизма абстракции и работайте на уровне виртуального DOM (например, используя render-функции).

  2. Если у вас есть пользовательская директива, которую нельзя легко заменить компонентами, вы можете предоставить её «серверный вариант» с помощью опции directives при создании серверного рендерера.