В феврале 2018 г. мы провели продвинутый курс по использованию стека MERN для создания масштабируемых, готовых к работе приложений JavaScript в рамках нашей программы ускорения. Немного позже мы изменили структуру продвинутых классов, и этот класс просто болтался без дела, почти забытый… Сначала посмотрите часть 1 здесь!

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

Интернационализация

Согласно общепринятым определениям интернационализации (i18n) и локализации (l10n) мы должны сказать, что mern-starter интернационализирован, поскольку он поддерживает локализацию.

🎯 В этом разделе мы:

1. Перевести некоторые этикетки
2. Локализовать даты
3. Встроить поддержку локализации данных в нашу базу данных

Добавление локализованных сообщений

В проекте используется библиотека react-intl от Yahoo, которая использует встроенный в JavaScript API «Интернационализация и основывается на нем».

Перевести этикетки

Наши сведения о продукте включают этикетки, которые доступны только на английском языке.

🎓 Добавьте переводы в пакеты сообщений en.js и fr.js.

Вы можете использовать переводчик Google. Проверьте результаты, нажав кнопку «Переключить язык» в заголовке. Элементы управления могут быть скрыты за инструментами разработки Redux.

🎓 Обратитесь к переведенным сообщениям с помощью компонента FormattedMessage.

Ищите примеры в модуле post. Вы всегда можете обратиться к документации.

Добавьте третий язык по вашему выбору

🎓 Добавьте третий пакет сообщений и переведите сообщения, используемые в настоящее время нашим веб-приложением.

Используйте другой локализованный контент

Библиотека react-intl включает множество функций для локализации различных типов информации. Обо всем этом вы можете прочитать на проекте Вики.

Локализуйте поле dateAdded

Обратитесь к документации о том, как локализовать поле Date в наших продуктах.

Локализация динамического контента

На данный момент все наши переводы добавлены в языковые пакеты. Но что, если мы хотим перевести контент в нашей базе данных? Стоит ли обновлять пакеты и повторно развертывать их каждый раз, когда необходимо перевести новые продукты?

Перевести названия продуктов

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

🎓 Обновите схему, чтобы она выглядела так:

const shopSchema = new Schema({
    name: {
        defaultMessage: { type: String, required: true },
        translations: [{ language: String, message: String }],
    },
    dateAdded: { type: Date, default: Date.now, required: true },
});

Повторно инициализируйте свои фиктивные данные.

🎓 Теперь добавьте несколько переводов названий продуктов в фиктивные данные и повторно инициализируйте базу данных. Вот пример:

new Shop({
    name: {
      defaultMessage: "Skateboard",
      translations: [
        { language: "fr", message: "planche à roulette" },
      ]
    },
  })

Показать переведенные названия продуктов

Наша цель — перевести названия продуктов и сделать это таким образом, чтобы их можно было использовать повторно. Итак, начнем с создания общего компонента с помощью инструмента merng. Мы собираемся заставить его ожидать свойства в той же форме, что и наша модель базы данных.

🎓 Используйте следующую команду:

merng functional-s ModelTranslation

Это будут ваши propTypes:

ModelTranslation.propTypes = {
  defaultMessage: PropTypes.string.isRequired,
  translations: PropTypes.arrayOf(PropTypes.shape({
    language: PropTypes.string.isRequired,
    message: PropTypes.string.isRequired,
  })),
};

Обратите внимание, как это согласуется с нашей моделью.

В нашем компоненте мы хотим выбрать правильный перевод. Чтобы получить доступ к текущей выбранной локали, вам нужно будет использовать React context API.

🎓 Чтобы предоставить вашему функциональному компоненту доступ к правильному контексту, вам нужно добавить эту строку:

ModelTranslation.contextTypes = { intl: PropTypes.object }

Поскольку форма наших свойств и наша модель совпадают, мы можем очень легко обновить ProductListItem, чтобы использовать наш новый компонент локализации:

<ModelTranslation {...props.name}/>

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

Расширенные возможности

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

Рендеринг на стороне сервера

Рендеринг на стороне сервера имеет некоторые преимущества и некоторые издержки, но в целом пользователи лучше реагируют на страницы, когда они отображаются быстро. В проекте MERN Starter уже настроен SSR, и его использование работает из коробки.

До сих пор мы запускали наше приложение в режиме разработки. Чтобы показать эффекты рендеринга на стороне сервера, давайте запустим производственную сборку. Сначала завершите сервер разработки, введя exit <enter> в окне процесса. Затем выполните следующую команду, чтобы запустить производственную сборку на локальном сервере:

npm run bs

Когда вы обновите страницу, вы сможете увидеть, как продукты мигают. Проект MERN Starter должен предоставить нам рендеринг на стороне сервера бесплатно! Что происходит?

Когда мы создали наш ProductsPage, мы скопировали страницу блога. Вы могли заметить следующий фрагмент кода:

// Actions required to provide data for this component to render in sever side.
PostListPage.need = [() => { return fetchPosts(); }];

Комментарий уже говорит нам, что происходит. Проект mern-starter отображает наши страницы на сервере, но ему нужно знать, когда страница будет готова. Нам часто нужно откуда-то загрузить данные (будь то API или база данных) до того, как наша страница будет «завершена», и нам нужно сообщить логике рендеринга на стороне сервера, какие действия избыточности необходимо выполнить, прежде чем отправлять обработанную разметку на сервер. браузер. Теперь добавьте это свойство массива need к вашему ProductsPage с действием, которое загружает продукты. Перезапустите производственную сборку, чтобы проверить, работает ли она.

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

Удалить избыточный вызов API

Несмотря на то, что список продуктов отправляется в браузер, наше приложение по-прежнему вызывает API для получения списка после того, как React его отобразит. Это потому, что мы загружаем его из метода жизненного цикла, который просто загружает его безоговорочно.

Добавьте логическое поле initialLoadComplete в редьюсер вашего магазина initialState и установите значение false. Теперь обновите функцию редуктора, чтобы установить значение true, когда продукты поступают из API. Наконец, обновите свой ProductsPage, чтобы он вызывал вызов API только при необходимости.

Перезагрузите приложение, чтобы убедиться, что вызов API устранен.

Разделение кода

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

Изучите связки

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

… и обратите внимание, что происходит, когда вы загружаете другую страницу.

Webpack разделил наш javascript на несколько файлов. Код для разных страниц упакован в отдельные пакеты.

Избавление от vendor.js

Запустите команду npm run build. Вывод показывает вам все файлы, которые генерирует webpack. Чтобы было понятнее, какой пакет к какой странице относится, давайте добавим имена к операторам require. Откройте файл clients/routes.js и измените компонент IndexRoute, добавив имя в качестве последнего параметра к ensure:

<IndexRoute
      getComponent={(nextState, cb) => {
        require.ensure([], require => {
          cb(null, require('./modules/Post/pages/PostListPage/PostListPage').default);
        }, 'PostListPage');
      }}
    />

И сделайте то же самое для других маршрутов. Теперь повторно запустите команду npm run build и проверьте вывод.

Откройте webpack.config.prod.js и найдите упоминания термина vendor. Удалите его как явную точку входа. Запустите команду сборки еще раз и проверьте, существует ли она.

Файл vendor.js все еще будет там, но теперь он крошечный. Между тем основной пакет js приложения вырос, потому что react и react-dom теперь являются его частью. Теперь удалите конфигурацию CommonsChunkPlugin и повторно запустите команду сборки. Файл поставщика больше не существует. Запустите рабочий сервер npm run start:prod, чтобы проверить, все ли загружается правильно.

К сожалению, на нашей странице произошла ошибка. Исправьте эту последнюю ссылку на vendor.js и обновите конфигурацию разработчика веб-пакета, чтобы она соответствовала рабочей версии.

Лучшие практики кодирования

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

Модули

Разделение кода на модули — это способ организовать проект по функциям. Хотя решение о том, что входит в модуль и как они взаимодействуют/соединяются, остается за разработчиком, посыл ясен: держите отдельные вещи, которые не связаны друг с другом.

Асинхронные компоненты

Когда мы смотрели на разделение кода, мы заметили использование require.ensure. Это загружает страницу лениво, только когда это необходимо. Причина, по которой это работает, заключается в том, что React Router позволяет нам загружать компонент асинхронно, используя свойство getComponent. Думать об этом:

  1. Как мы можем предварительно загрузить отдельные страницы, но при этом получить преимущества разделения кода?
  2. Какие еще ресурсы мы можем лениво загружать, используя аналогичную технику (думаю, «более гранулированную»).

Селекторы

В модуле post файл PostReducer.js содержит не только функцию редюсера. Он также определяет некоторые селекторы. Селекторы — это очень простые функции, которые возвращают части объекта состояния. Подробнее о селекторах можно прочитать здесь. Но все сводится к принципу дизайна: разделение задач. Вы не хотите, чтобы ваши компоненты знали или заботились о форме вашего избыточного состояния, потому что это усложняет изменение и повторное использование.

Обновление <head> из внутренних компонентов

Вы когда-нибудь хотели изменить свою страницу <title> внутри компонента? Вы получили прямой доступ к DOM? Альтернативой является react-helmet, который дает вам общее решение этой проблемы, позволяя вам манипулировать всеми тегами <head>. Это прекрасно отделяет ваш компонент/шаблон-оболочку от компонентов более низкого уровня.

Вся конфигурация среды

Проект экономит вам МНОГО времени, определяя рабочий процесс разработки/тестирования/производства. Благодаря заглушкам тесты могут выполняться без браузера (см. server/util/setup-test-env.js). Вызовы API работают одинаково независимо от среды (см. client/util/apiCaller.js).

Вывод

Как вам это понравилось? Вы когда-нибудь использовали MERN раньше? Как вам понравилось работать с ним? Дайте нам знать в комментариях ниже!