Передача конфигурации в Vue.js
Как передать конфигурацию и начальное состояние приложения с сервера в приложение Vue.js?
Перевод статьи: Michał Męciński Passing configuration to Vue.js
В предыдущей статье я писал о внедрении зависимостей в компоненты Vue и хранилище Vuex. Я также продемонстрировал, как можно использовать фабричные функции для передачи зависимостей и параметров конфигурации объектам и простым функциям.
Напомним, что последний файл main.js из этой статьи:
import Vue from 'vue' import makeI18n from './i18n' import makeAjax from './services/ajax' import makeStore from './store' import App from './components/App.vue' const i18n = makeI18n( 'en' ); const ajax = makeAjax( 'http://example.com' ); const store = makeStore( i18n, ajax ); new Vue( { el: '#app', i18n, ajax, store, render: h => h( App ) } );
Как видите, мы передаем языковой стандарт модулю i18n, а адрес сервера — модулю ajax. Однако эти параметры все еще жестко запрограммированы в сценарии.
Предположим, что и локаль, и адрес сервера основаны на каком-то условии, например, на предпочитаемом пользователем языке или URL-адресе страницы. Вместо того, чтобы жестко кодировать эти значения в скрипте, мы хотим иметь возможность получать их с сервера.
Конечно, мы могли бы получить эти опции, используя запрос AJAX. Однако такое решение будет неэффективным, поскольку каждый раз при загрузке приложения будет требоваться дополнительный запрос. Это может привести к ненужной задержке до инициализации и визуализации приложения.
Внедрение конфигурации на страницу
Распространенным решением этой проблемы является внедрение конфигурации непосредственно в тело страницы. Это работает, если страница динамически отображается сервером, например, с помощью сценария PHP, и сервер может определить эти параметры конфигурации и передать их клиентскому коду.
Это можно сделать разными способами, например, с помощью атрибутов данных HTML, но наиболее гибкий способ — это вставить данные JSON в отдельный тег <script>
:
<script id="config" type="application/json">{"locale":"en","baseURL":"http:\/\/example.com"} </script>
Он имеет атрибут id, чтобы его было легко отличить от других тегов скрипта на той же странице. Кроме того, тип контента установлен на application/json, чтобы браузер не интерпретировал данные как код JavaScript. Когда браузер сталкивается с таким тегом сценария, он не выполняет его никоим образом, но тег и его содержимое доступны в дереве DOM, поэтому это отличное решение для хранения данных любого типа.
Обратите внимание, что символы косой черты в строке URL-адреса экранируются обратной косой чертой. Это может показаться ненужным, поскольку косые черты не имеют какого-либо особого значения в контексте строки JSON. Чтобы понять, почему это важно, представьте себе такой сценарий, когда некоторый пользовательский ввод становится частью данных JSON:
<script id="config" type="application/json"> {"locale":"en","userName":"</script><h1>Hello!</h1><script>"} </script>
Анализатор HTML будет интерпретировать тег </script> как конец сценария, даже если он отображается внутри строки, поскольку он ничего не знает о синтаксисе JSON. Затем он будет интерпретировать следующий тег <h1> как часть тела документа. Таким образом, с точки зрения браузера, приведенная выше разметка интерпретируется следующим образом:
<script id="config" type="application/json"> {"locale":"en","userName":" </script> <h1>Hello!</h1> <script>"}</script>
Это может привести к потенциально опасным местам. Поэтому мы пользуемся тем фактом, что анализатор JSON интерпретирует последовательность \ / внутри строки как обычную косую черту, а следующие данные корректно (и безопасно) интерпретируются:
<script id="config" type="application/json"> {"locale":"en","userName":"<\/script><h1>Hello!<\/h1><script>"} </script>
Функция json_encode() в PHP уже по умолчанию экранирует такие слэши, поэтому ее можно безопасно использовать.
Вот пример кода PHP, который вводит данные конфигурации на страницу:
<?php $code[ 'locale' ] = get_user_locale(); $code[ 'baseURL' ] = get_server_base_url(); ?> <script id="config" type="application/json"> <?php echo json_encode( $config ) ?> </script>
Клиентский скрипт теперь может читать конфигурацию из тега скрипта, используя следующий код:
const configElement = document.getElementById( 'config' ); const config = JSON.parse( configElement.innerHTML );const i18n = makeI18n( config.locale ); const ajax = makeAjax( config.baseURL );
Как видите, мы просто находим элемент script, используя его id, а затем анализируем его содержимое как объект JSON. Наконец, мы передаем параметры конфигурации заводским функциям, которые инициализируют наши модули.
Обратите внимание, что документ должен быть загружен до выполнения сценария, в противном случае элемент сценария не будет найден в дереве DOM. Распространенный способ убедиться, что это включает тег script, который загружает код на стороне клиента в конце тела документа, сразу после данных конфигурации.
Передача конфигурации явно
Немного другой подход заключается в изменении сценария main.js, чтобы он экспортировал функцию:
import Vue from 'vue' import makeI18n from './i18n' import makeAjax from './services/ajax' import makeStore from './store' import App from './components/App.vue' export function main( { locale, baseURL } ) { const i18n = makeI18n( locale ); const ajax = makeAjax( baseURL ); const store = makeStore( i18n, ajax ); new Vue( { el: '#app', i18n, ajax, store, render: h => h( App ) } ); }
Чтобы сделать экспортированную функцию доступной, сценарий должен быть связан как библиотека. Я описал, как это можно сделать с помощью веб-пакета, в одной из моих предыдущих статей. Короче говоря, файл webpack.config.js должен включать параметр library в разделе output, например:
output: { path: path.resolve( __dirname, './assets' ), filename: 'js/myapp.js', library: 'MyApp' }
Это делает экспортированные символы доступными в качестве свойств объекта, назначенного глобальной переменной MyApp. Таким образом, функция main() в нашем скрипте может быть вызвана так:
<script> MyApp.main({"locale":"en","baseURL":"http:\/\/example.com"}); </script>
Это требует лишь небольшого изменения в коде на стороне сервера:
<?php $code[ 'locale' ] = get_user_locale(); $code[ 'baseURL' ] = get_server_base_url(); ?> <script> MyApp.main(<?php echo json_encode( $config ) ?>); </script>
Это решение имеет два преимущества. Тег <script>, который загружает код на стороне клиента, теперь может быть включен в заголовок страницы, поэтому он может быть загружен и скомпилирован браузером до полной загрузки страницы. В конец тела страницы должен быть включен только вызов main().
Второе преимущество заключается в том, что параметры конфигурации являются более явными, поскольку они объявлены в качестве параметров функции main(). Вы можете использовать любое решение, которое лучше всего подходит для вас в конкретном сценарии.
Начальное состояние Vuex
Очень часто приложению Vue.js также необходимо загружать некоторые начальные данные при запуске, например, начальное состояние хранилища Vuex. Загрузка этих данных с помощью AJAX-запроса добавляет задержку перед тем, как приложение будет обработано и готово к использованию.
Чтобы избежать этой задержки, начальное состояние приложения может быть введено непосредственно в страницу, как и конфигурация.
Давайте изменим main.js следующим образом:
import Vue from 'vue' import makeI18n from './i18n' import makeAjax from './services/ajax' import makeStore from './store' import App from './components/App.vue' export function main( { locale, baseURL, ...initialState } ) { const i18n = makeI18n( locale ); const ajax = makeAjax( baseURL ); const store = makeStore( i18n, ajax, initialState ); new Vue( { el: '#app', i18n, ajax, store, render: h => h( App ) } ); }
Вы можете видеть, что теперь все опции, кроме locale и baseURL, назначены объекту initialState и переданы функции makeStore().
Давайте изменим store/index.js соответственно:
import Vue from 'vue' import Vuex from 'vuex' import makeModule1 from './modules/module1' Vue.use( Vuex ); export default function makeStore( i18n, ajax, initialState ) { return new Vuex.Store( { state: makeState( initialState ), actions: makeActions( i18n, ajax ), modules: { module1: makeModule1( i18n, ajax, initialState ) } } ); } function makeState( initialState ) { return { ... }; } function makeActions( i18n, ajax ) { return { ... }; }
Состояние хранилища Vuex теперь создается фабричной функцией, которая принимает введенные данные в качестве параметра.
Обратите внимание, что я заимствовал идею введения начального состояния приложения на страницу из этой статьи Anthony Gore.
Имейте в виду, что если исходное состояние содержит большой объем данных, загрузка страницы займет больше времени. В некоторых сценариях может быть лучше ввести только самую важную информацию, которая необходима во время первого рендеринга приложения. Это ускорит загрузку и рендеринг, а оставшиеся данные могут быть загружены асинхронно с помощью запроса AJAX.