Написание универсального кода
Прежде чем идти дальше, давайте немного обсудим ограничения при написании «универсального» кода — кода, который выполняется как на сервере, так и на клиенте. Из-за различий в 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. Существует два способа обойти это:
Предпочитайте использовать компоненты в качестве механизма абстракции и работайте на уровне виртуального DOM (например, используя render-функции).
Если у вас есть пользовательская директива, которую нельзя легко заменить компонентами, вы можете предоставить её «серверный вариант» с помощью опции
directives
при создании серверного рендерера.