17 Июля 2018 JavaScript Перевод

7 советов для создания большого приложения на Nuxt

img

Когда дело доходит до структуры кода, Nuxt становится упрямым. Его соглашения экономят вам много времени на принятие решений. Через год использования Nuxt на больших кодовых базах, я стал радоваться тому, что в нем есть значительное пространство для собственных настроек. В этой статье изложено несколько советов, которые либо упростили общие шаблоны кода, либо помогли мне лучше управлять большими кодовыми базами Nuxt.

Имейте в виду, что эта статья касается Nuxt версии 1.4.x. На момент написания статьи, уже ведётся работа над существенно обновленной версией 2.0. Кроме того, Nuxt в основном известен как инструмент SSR, но он отлично способен создавать и SPA-приложения. Мне нравится тот факт, что Nuxt предлагает стандарт организации кодовой базы для всех приложений Vue.

Использование файла index для пользовательских маршрутов

Последняя версия Nuxt включает в себя extendRoutes() - способ добавления пользовательских роутеров к автоматической настройке маршрутов Nuxt на основе каталога pages/. Также можно полностью обойти настройку Nuxt, используя файл index для маршрутов. Вам все равно нужно будет использовать pages в качестве каталога, но вы можете добавить index.js к нему:

module.exports = [
  {
    name: 'my-route',
    path: '/my-route',
    component: 'src/pages/foobar.vue'
  }
]

В nuxt.config.js используйте это как extensionRoutes():

extendRoutes (nuxtRoutes, resolve) {
 nuxtRoutes.splice(0, nuxtRoutes.length, ...routes.map((route) => {
   return { ...route, component: resolve(__dirname, route.component) }
 }))
}

Пользовательский компонент loading

Вы можете изменить компонент loading который существует в Nuxt по умолчанию, установив свойство загрузки в nuxt.config.js. Что сразу не очевидно, так это то, что можно получить доступ к нему из хранилища Vuex от Nuxt. Это может помочь продлить время работы полосы загрузки, пока есть активные HTTP-запросы, запускаемые приложением. Один общий шаблон, который я использую - это установка мутации setActive, которая принимает значение 1 или -1 для определения начала и конца HTTP-запроса. Затем я проверяю состояние active в Vuex перед отключением загрузчика:

<template>
 <div class="loader" />
</template>

<script>
import { mapState } from 'vuex'
export default {
 data: () => ({
   loader: null,
   watching: false
 }),
 computed: mapState({
   active: (state) => state.active
 }),
 watch: {
   active (isActive) {
     if (this.watching && !isActive) {
       // clear loader
       this.watching = false
     }
   }
 },
 methods: {
   start () {
     // start loader
     this.watching = true
   },
   finish () {
     if (this.active) {
       this.watching = true
     } else {
       // clear loader
     }
   }
 }
}
</script>

В зависимости от скорости рендеринга приложения вы можете настроить поведение загрузчика с задержкой setTimeout или даже добавить дополнительные методы для загрузчика, которые отключают исходные методы start() и finish(). В моих приложениях я добавил метод startNow(), который мгновенно открывает загрузчик до того, как фактически произойдет какой-либо переход на маршрут, и метод finishNow(), который отключает загрузчик только после завершения API запросов, аналогично тому, что показано выше.

Передача данных из контекста Коа

При добавлении защиты CSRF в приложение мне пришлось передать токен CSRF, сгенерированный koa-csrf, в nuxtServerInit(). Проблема в том, что nuxtServerInit() дает вам ссылки req и res, но не содержит ссылок на собственный контекст Koa. Решение, которое я нашел, это скопировать любые переменные контекста, которые мне нужны для объекта res, который передается в Nuxt, как показано ниже:

ctx.res.csrf = ctx.csrf 
return new Promise((resolve, reject) => {
 ctx.res.on('close', resolve)
 ctx.res.on('finish', resolve)
 nuxt.render(ctx.req, ctx.res, (promise) => {
   promise.then(resolve).catch(reject)
 })
})

Использование фабричной функции хранилища Vuex

Nuxt имеет очень практичный способ настроить хранилище Vuex, автоматически подбирая подмодули из каталога store/. Также можно сделать еще один шаг и использовать класс или функцию для создания своего глобального хранилища.

Один шаблон, который мне нравится использовать - файл main.js в корневом каталоге Nuxt, который определяет моё глобальное хранилище Vuex как класс. Для этого я использую небольшой вспомогательный класс, называемый apistore. С его помощью мой store/index.js выглядит так:

import { MyAppVuexStore } from '@/main'

const store = MyAppVuexStore.makeStore()

export const state = store.state
export const getters = store.getters
export const mutations = store.mutations
export const actions = store.actions

В MyAppVuexStore я в основном определяю действия Vuex как методы экземпляра, метод экземпляра init становится nuxtServerInit(), и глобальное состояние определяется как метод экземпляра. Я также могу использовать APIStore.use() для объединения хранилищ вместе в глобальном хранилище, сохраняя при этом файлы подмодулей в store/ по мере необходимости. Лично я оставляю это место для хранилищ, связанных с API, то есть сохраняю отслеживаемые данные удаленных API. Таким образом, я могу делать по одному сабмодулю хранилища для каждого ресурса API.

export class MyAppVuexStore {
 state () {
 }
 init () { // nuxtServerInit
 }
 someAction () {
 }
}

Вы можете расширить класс помощника apistore, чтобы использовать методы класса в качестве мутаций или методы getter как хранилища для геттеров, если хотите. В моем коде я стараюсь использовать update мутацию apistore (которая обновляет все props, которые определены в payload) для глобального хранилища, и регулярный код мутации для подмодулей хранилища.

Генерация дополнительных файлов с extend()

Если вы хотите расширить компилятор Nuxt чем-то своим, но не хотите полностью встраивать Nuxt плагин, вы можете добавить функцию build.extend в nuxt.config.js, которая будет записывать (fs.writeFileSync()) что-то в ваш исходный каталог, и все равно сборщик Nuxt его подхватит. Я использовал это недавно, чтобы автоматически заполнить серию диспетчеров API для серверных методов:

const api = require('../server/api')

const formatAPIMethod = (sig, func) => {
 return func.toString()
   .replace(/__apiMethod__/, `apiMethod: '${sig}'`)
   .replace(/\n {2}/g, '\n')
}

exports.genAPIMethods = function () {
 let notice = `// This file is autogenerated\n\n`
 let file = `${notice}module.exports = (store) => ({`
 const funcs = []
 Object.keys(api).forEach((r) => {
   file += `\n ${r}: `
   const methodDefs = JSON.stringify(api[r], (k, v) => {
     if (typeof v === 'function') {
       funcs.push(k)
       return '__function__'
     } else {
       return v
     }
   }, 4)
   .replace(/\n}/g, '\n },')
   file += methodDefs
   .replace(/\n(\s+)"([^"]+)"/g, (_, ws, name) => {
     return `\n${ws}${name}`
   })
   .replace(/"__function__"/g, (m) => {
     // The following is needed so ESLint accepts this
     /* global store __apiMethod__ */
     return formatAPIMethod(`${r}.${funcs.shift()}`, (payload, shouldDispatch = true) => {
       return store.dispatch('api', {
         __apiMethod__,
         payload,
         shouldDispatch
       }, {root: true})
     })
   })
 })
 file = file.slice(0, -1)
 file += '\n})\n'
 return file
}

Затем я перехожу к вызову genAPIMethods() прямо в начале builder.extend(). Благодаря функциям Function.prototype.toString() и JSON.stringify() для фильтрации (и тегов) неизвестных типов JSON, я смог автоматически генерировать файл со всеми API dispatchers (посредством действий Vuex), с моего серверного API файла:

module.exports = (store) => ({
 resource: {
   method: (payload, shouldDispatch = true) => {
     return store.dispatch('api', {
       apiMethod: 'resource.method',
       payload,
       shouldDispatch
     }, {root: true})
   }
 ...

Инициализация глобального клиентского кода

Когда экземпляр Nuxt загружается, он запускает window.onNuxtReady(app), передавая app как первый и единственный параметр. Вы можете использовать это для выполнения глобального клиентского кода при инициализации, рабочих служб или скриптов отслеживания рекламы и т.д. В моем помощнике apistore я использую статический метод client для его определения, поэтому мой код onNuxtReady(), может определяться в файле main.js.

export class MyAppVuexStore {
 static client (app) {
   app.$store.dispatch('someInitAction', 'from client init code')
 }
}

Перехватчики запросов Axios

Я использовал axios в качестве своей сетевой библиотеки, пока работал с Nuxt. Эта библиотека никогда не подводила меня. Моей любимой особенностью axios являются её перехватчики запросов и ответов. К счастью для Nuxt есть nuxt-axios, который позволяет нам определять их как плагины, если необходимо:

export default function ({ $axios }) {
 $axios.onRequest((config) => {
   // ...
   // Refresh JWT token if needed
   // ...
   config.headers['Authorization'] = `Bearer ${token}`
   return config
 })
}

С nuxt-axios у вас имеется экземпляр $axios, доступный как на серверном, так и на клиентском коде, который вы можете легко использовать с одними и теми же сетевыми методами. Помните, что вы также можете создать свой API proxy на сервере, минуя подобные проблемы с API на клиенте. Для получения дополнительной информации ознакомьтесь с моим шаблоном Nuxt и Koa.

Оригинал: 7 Tips For Building A Large Nuxt App