Передача конфигурации в Vue.js

Spread the love

Как передать конфигурацию и начальное состояние приложения с сервера в приложение 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.


Spread the love

Добавить комментарий

Ваш e-mail не будет опубликован.