🚀Осветите свой стек технологий Vue, вот практические заметки 4D Nuxt.js

Nuxt.js
🚀Осветите свой стек технологий Vue, вот практические заметки 4D Nuxt.js

предисловие

Как Vuer (vue-разработчик), если вы не знаете этот фреймворк, то вашVueСтек технологий еще не засветился.

Что такое Nuxt.js

Официальное введение Nuxt.js:

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

если вы знакомы сVue.js, то вы можете начать в ближайшее времяNuxt.js. опыт разработки иVue.jsразницы особой нет, все равноVue.jsРасширил некоторые настройки. Конечно жеNode.jsЕсли у вас есть основа, то это лучше.

Какую проблему решает Nuxt.js

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

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

Мой первый проект Nuxt.js

Я также использую его в свободное времяNuxt.jsИмитация самородковwebВеб-сайт:

nuxt-juejin-projectэто использованиеNuxt.jsЧтобы имитировать учебный проект Nuggets, в основном используйте:nuxt + koa + vuex + axios + element-ui. Все данные этого проекта синхронизируются с Nuggets, т.к. интерфейс черезkoaВперед как промежуточный слой. Данные главной страницы выполняются посредством рендеринга на стороне сервера.

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

В предисловии к проекту есть скриншоты, если можешь джио, приходи на звезду😜~

адрес проекта:GitHub.com/Chan wah fun G…

Базовое приложение и конфигурация

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

🏃‍♀️ бегиУ-у-у. Гнев бьет быстрее. Талант/дорого/ИНС его...

Что касается конфигурации проекта, я выбрал:

  • Сервер: Коа
  • Платформа пользовательского интерфейса: элемент пользовательского интерфейса
  • Тестовая среда: Нет
  • Новый режим: универсальный
  • Использование встроенного Axios
  • Использование ЭсЛинт

context

context — это дополнительно предоставляемый объект из Nuxt, который используется в специальных областях жизненного цикла Nuxt, таких как «asyncData», «плагины», «промежуточное ПО», «модули» и «store/nuxtServerInit».

Итак, хотите использоватьNuxt.js, мы должны знать, какие свойства доступны для объекта.

contextОфициальное описание документа нажмите здесьwww.nuxtjs.cn/api/context

Ниже я перечисляю несколько свойств, которые являются более важными и обычно используются в практических приложениях:

app

appдаcontextнаиболее важное свойство в , как мыVueсерединаthis, в него будут смонтированы глобальные методы и свойства. Из-за специфики рендеринга на стороне сервера многиеNuxtПредоставленные жизненные циклы выполняются на стороне сервера, что означает, что им будет предшествоватьVueсоздание экземпляра. Итак, в течение этих жизней мы не можем пройтиthisдля получения методов и свойств экземпляра. использоватьappЧтобы компенсировать это, мы обычно внедряем глобальные методы вthisиapp, используется в жизненном цикле сервераappчтобы получить доступ к этому методу и использовать его в клиентеthis, чтобы обеспечить обмен методами.

Например:

Предположение$axiosбыли введены одновременно, как правило, основные данные передаются черезasyncData(Этот жизненный цикл инициирует запрос и отправляет полученные данные на сервер и объединяет их в html для возврата), чтобы запросить заранее для рендеринга на стороне сервера, а вторичные данные проходят через клиентскийmountedзапрашивать.

export default {
  async asyncData({ app }) {
    // 列表数据
    let list = await app.$axios.getIndexList({
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    return {
      list
    }
  },
  data() {
    return {
      list: [],
      categories: []
    }
  },
  async mounted() {
    // 分类
    let res = await this.$axios.getCategories()
    if (res.s  === 1) {
      this.categories = res.d
    }
  }
}

store

storeдаVuex.Storeэкземпляр во время выполненияNuxt.jsПопытаюсь найти корневой каталог приложенияstoreкаталог, если каталог существует, он добавит файлы модуля в конфигурацию сборки.

Итак, нам нужен только корневой каталогstoreСоздайте файл модуля js, и вы можете его использовать.

/store/index.js :

export const state = () => ({
  list: []
})

export const mutations = {
  updateList(state, payload){
    state.list = payload
  }
}

иNuxt.jsбудем очень любезны помочь намstoreВ то же время инъекция, и, наконец, мы можем использовать ее в компоненте следующим образом:

export default {
  async asyncData({ app, store }) {
    let list = await app.$axios.getIndexList({
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    // 服务端使用
    store.commit('updateList', list)
    return {
      list
    }
  },
  methods: {
    updateList(list) {
      // 客户端使用,当然你也可以使用辅助函数 mapMutations 来完成
      this.$store.commit('updateList', list)
    }
  }
}

понятьstoreПроцесс инъекции, я читал.nuxt/index.jsисходный код (.nuxtкаталогNuxt.jsавтоматически генерируется во время выполнения сборки), вероятно, зная процесс. первый в.nuxt/store.jsв, даstoreФайлы модуля выполняют серию обработки и выставляютcreateStoreметод. затем в.nuxt/index.jsсередина,createAppМетод будет вводить его одновременно:

import { createStore } from './store.js'

async function createApp (ssrContext) {
  const store = createStore(ssrContext)
  // ...
  // here we inject the router and store to all child components,
  // making them available everywhere as `this.$router` and `this.$store`.
  // 注入到this
  const app = {
    store
    // ...
  }
  // ...
  // Set context to app.context
  // 注入到context
  await setContext(app, {
    store
    // ...
  })
  // ...
  return {
    store,
    app,
    router
  }
}

Кроме того, я также нашелNuxt.jsпройдетinjectспособ его крепленияplugin(pluginЭто основной способ монтирования (глобальный метод, о котором будет сказано позже, если вы не знаете, то можете его сначала игнорировать), то есть вstore, мы можем пройтиthisДоступ к глобальному методу:

export const mutations = {
  updateList(state, payload){
    console.log(this.$axios)
    state.list = payload
  }
}

параметры, запрос

paramsиqueryсоответственноroute.paramsиroute.queryпсевдоним. Все они являются объектами с параметрами маршрута, и ими легко пользоваться. Об этом нечего сказать, все кончено.

export default {
  async asyncData({ app, params }) {
    let list = await app.$axios.getIndexList({
      id: params.id,
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    return {
      list
    }
  }
}

redirect

Этот метод перенаправляет запрос пользователя на другой маршрут, обычно используемый для проверки авторизации. использование:redirect(params),paramsпараметр содержитstatus(код состояния, по умолчанию 302),path(маршрутный путь),query(параметр), гдеstatusиqueryявляется необязательным. Конечно, если вы просто перенаправляете маршрут, вы можете передать строку пути, напримерredirect('/index').

Например:

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

export default function ({ redirect }) {
  // ...
  if (!token) {
    redirect({
      path: '/login',
      query: {
        isExpires: 1
      }
    })
  }
}

error

Этот метод переходит на страницу ошибки. использование:error(params),paramsпараметры должны содержатьstatusCodeиmessageполе. В реальных сценариях всегда есть какие-то необоснованные операции, поэтому страница не может отобразить действительно желаемый эффект, все равно необходимо использовать этот метод для сообщений об ошибках.

Например:

Данные запроса страницы сведений о теге зависят отquery.name,когдаquery.nameЕсли он не существует, запрос не может вернуть доступные данные и в это время переходит на страницу с ошибкой.

export default {
  async asyncData({ app, query, error }) {
    const tagInfo = await app.$api.getTagDetail({
      tagName: encodeURIComponent(query.name)
    }).then(res => {
      if (res.s === 1) {
        return res.d
      } else {
        error({
          statusCode: 404,
          message: '标签不存在'
        })
        return
      }
    })
    return {
      tagInfo
    }
  }
}

Жизненный цикл следующей общей страницы

asyncData

Вы, вероятно, хотите получить и отобразить данные на стороне сервера. Nuxt.js добавляет метод asyncData, который позволяет асинхронно извлекать данные перед рендерингом компонента.

asyncDataЭто наиболее часто используемый и важный жизненный цикл, а также ключевой момент рендеринга на стороне сервера. Жизненный цикл ограничен вызовами компонентов страницы, и первый параметрcontext. Время его вызова — до инициализации компонента, работающего в серверной среде. так вasyncDataжизненный цикл, мы не можем пройтиthisсо ссылкой на текущийVueэкземпляр, ниwindowобъект иdocumentОбъекты, это то, на что нам нужно обратить внимание.

обычно вasyncDataДанные главной страницы будут запрошены заранее, а полученные данные будут склеены серверомhtmlВернитесь к внешнему рендерингу, чтобы улучшить скорость загрузки и прогресс.seoоптимизация.

Посмотрите на рисунок ниже, в инструменте отладки Google вы не видите основной интерфейс данных для инициирования запроса, только возвращаемыйhtmlДокументация, подтверждающая, что данные рендерятся на сервере.

Наконец, вам нужно вернуть данные, полученные интерфейсом:

export default {
  async asyncData({ app }) {
    let list = await app.$axios.getIndexList({
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    // 返回数据
    return {
      list
    }
  },
  data() {
    return {
      list: []
    }
  }
}

Стоит отметить, чтоasyncDataОн выполняется только на первом экране, в других случаях он эквивалентенcreatedилиmountedОтрисовка страницы на стороне клиента.

Что это обозначает? Например:

Теперь есть две страницы, домашняя страница и страница сведений, на каждой из которых есть настройки.asyncData. При входе на главную страницуasyncDataзапустить на сервере. После завершения рендеринга нажмите на статью, чтобы перейти на страницу сведений.asyncDataОн не запускается на сервере, а инициирует запрос на клиенте для получения данных, поскольку страница сведений больше не является первым экраном. Когда мы обновляем страницу сведений, страница сведенийasyncDataбудет работать на стороне сервера. Итак, не вдавайтесь в это недоразумение (эй, разве это не рендеринг на стороне сервера, зачем вам все еще инициировать запрос?).

fetch

Метод fetch используется для заполнения данных дерева состояний (хранилища) приложения перед отрисовкой страницы. Он аналогичен методу asyncData, за исключением того, что он не устанавливает данные компонента.

Проверьте официальное описание, вы можете знать, что жизненный цикл используется для заполненияVuexдерево состояний, сasyncDataОпять же, он вызывается перед инициализацией компонента, первый параметрcontext.

Чтобы сделать процесс выборки асинхронным, вам нужно вернутьPromise,Nuxt.jsбуду ждать этогоpromiseВизуализируйте компонент после завершения.

export default {
  fetch ({ store, params }) {
    return axios.get('http://my-api/stars')
    .then((res) => {
      store.commit('setStars', res.data)
    })
  }
}

Вы также можете использовать шаблон async или await для упрощения кода следующим образом:

export default {
  async fetch ({ store, params }) {
    let { data } = await axios.get('http://my-api/stars')
    store.commit('setStars', data)
  }
}

Но это не значит, что мы можем толькоfetchзаполнить дерево состояний вasyncDataВ то же самое.

validate

Nuxt.js позволяет настроить метод проверки в компоненте страницы, соответствующем динамической маршрутизации, для проверки правильности параметров динамической маршрутизации.

Это может помочь нам при проверке легитимности параметров маршрутизации, первый параметрcontext. Немного отличается от вышеизложенного то, что мы можем получить доступ к методам экземпляраthis.methods.xxx.

Распечататьthisследующее:

Жизненный цикл может возвращатьBoolean, если true, введите маршрут, если false, остановите отрисовку текущей страницы и отобразите страницу с ошибкой:

export default {
  validate({ params, query }) {
    return this.methods.validateParam(params.type)
  },
  methods: {
    validateParam(type){
      let typeWhiteList = ['backend', 'frontend', 'android']
      return typeWhiteList.includes(type)
    }
  }
}

Или вернуть обещание:

export default {
  validate({ params, query, store }) {
    return new Promise((resolve) => setTimeout(() => resolve()))
  }
}

Ожидаемые или неожиданные ошибки также могут возникать во время выполнения функции проверки:

export default {
  async validate ({ params, store }) {
    // 使用自定义消息触发内部服务器500错误
    throw new Error('Under Construction!')
  }
}

watchQuery

Прислушивайтесь к изменениям строк параметров и выполняйте методы компонентов (asyncData, fetch, validate, layout,...)

watchQueryМожет быть установленBooleanилиArray(дефолт: []). использоватьwatchQueryСвойства могут прослушивать изменения строк параметров. Если определенная строка изменится, будут вызваны все методы компонента (asyncData, fetch, validate, layout, ...). Для повышения производительности отключено по умолчанию.

существуетnuxt-juejin-projectНа странице поиска проекта я также использую эту конфигурацию:

<template>
  <div class="search-container">
    <div class="list__header">
      <ul class="list__types">
        <li v-for="item in types" :key="item.title" @click="search({type: item.type})">{{ item.title }}</li>
      </ul>
      <ul class="list__periods">
        <li v-for="item in periods" :key="item.title" @click="search({period: item.period})">{{ item.title }}</li>
      </ul>
    </div>
  </div>
</template>
export default {
  async asyncData({ app, query }) {
    let res = await app.$api.searchList({
      after: 0,
      first: 20,
      type: query.type ? query.type.toUpperCase() : 'ALL',
      keyword: query.keyword,
      period: query.period ? query.period.toUpperCase() : 'ALL'
    }).then(res => res.s == 1 ? res.d : {})
    return {
      pageInfo: res.pageInfo || {},
      searchList: res.edges || []
    }
  },
  watchQuery: ['keyword', 'type', 'period'],
  methods: {
    search(item) {
      // 更新路由参数,触发 watchQuery,执行 asyncData 重新获取数据
      this.$router.push({
        name: 'search',
        query: {
          type: item.type || this.type,
          keyword: this.keyword,
          period: item.period || this.period
        }
      })
    }
  }
}

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

head

Nuxt.js использует vue-meta для обновления атрибутов Head и html приложения.

использоватьheadМетод задает тег заголовка текущей страницы, который можно передать черезthisПолучить данные компонента. Помимо хорошего внешнего вида, правильные настройкиmetaТеги также могут помочь поисковым системам найти страницу.seoоптимизация. Обычно устанавливаетсяdescription(Введение) иkeyword(Ключевые слова).

title:

meta:

export default {
  head () {
    return {
      title: this.articInfo.title,
      meta: [
        { hid: 'description', name: 'description', content: this.articInfo.content }
      ]
    }
  }
}

Чтобы избежать подкомпонентовmetaМетка не может корректно перекрывать ту же метку в родительском компоненте и вызывать дублирование.Рекомендуется использоватьhidключmetaЭтикетке присваивается уникальный идентификационный номер.

существуетnuxt.config.js, мы также можем установить глобальныйhead:

module.exports = {
  head: {
    title: '掘金',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover' },
      { name: 'referrer', content: 'never'},
      { hid: 'keywords', name: 'keywords', content: '掘金,稀土,Vue.js,微信小程序,Kotlin,RxJava,React Native,Wireshark,敏捷开发,Bootstrap,OKHttp,正则表达式,WebGL,Webpack,Docker,MVVM'},
      { hid: 'description', name: 'description', content: '掘金是一个帮助开发者成长的社区,是给开发者用的 Hacker News,给设计师用的 Designer News,和给产品经理用的 Medium。掘金的技术文章由稀土上聚集的技术大牛和极客共同编辑为你筛选出最优质的干货,其中包括:Android、iOS、前端、后端等方面的内容。用户每天都可以在这里找到技术世界的头条内容。与此同时,掘金内还有沸点、掘金翻译计划、线下活动、专栏文章等内容。即使你是 GitHub、StackOverflow、开源中国的用户,我们相信你也可以在这里有所收获。'}
    ],
  }
}

Пополнить

Ниже приведена последовательность вызова этих жизненных циклов, которая может нам помочь в какой-то момент.

validate  =>  asyncData  =>  fetch  =>  head

Настройте порт запуска

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

Первое

nuxt.config.js :

module.exports = {
  server: {
    port: 8000,
    host: '127.0.0.1'
  }
}

секунда

package.json :

"config": {
  "nuxt": {
    "port": "8000",
    "host": "127.0.0.1"
  }
},

Загружать внешние ресурсы

Глобальная конфигурация

nuxt.config.js :

module.exports = {
  head: {
    link: [
      { rel: 'stylesheet', href: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/atom-one-light.min.css' },
    ],
    script: [
      { src: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/highlight.min.js' }
    ]
  }
}

Конфигурация компонента

доступно в компонентеheadнастроить,headприемлемыйobjectилиfunction. Официальный пример используетobjectТип, используйтеfunctionТип тоже работает.

export default {
  head () {
    return {
      link: [
        { rel: 'stylesheet', href: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/atom-one-light.min.css' },
      ],
      script: [
        { src: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/highlight.min.js' }
      ]
    }
  }
}

переменная среды

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

Создайте переменные среды

nuxt.config.js :

module.exports = {
  env: {
    baseUrl: process.env.NODE_ENV === 'production' ? 'http://test.com' : 'http://127.0.0.1:8000'
  },
}

С приведенной выше конфигурацией мы создалиbaseUrlпеременные среды, черезprocess.env.NODE_ENVОпределите среду, соответствующую соответствующему адресу

Используйте переменные среды

Мы можем использовать следующие два способаbaseUrlПеременная:

  1. пройти черезprocess.env.baseUrl
  2. пройти черезcontext.env.baseUrl

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

/plugins/axios.js:

export default function (context) {
	$axios.defaults.baseURL = process.env.baseUrl
	// 或者 $axios.defaults.baseURL = context.env.baseUrl
	$axios.defaults.timeout = 30000
	$axios.interceptors.request.use(config => {
		return config
	})
	$axios.interceptors.response.use(response => {
		return response.data
	})
}

plugins

pluginsПоскольку это основной способ глобальной инъекции, необходимо освоить некоторые способы его использования. Иногда вы хотите использовать значение функции или свойства во всем приложении, и в этом случае вам нужно внедрить их вVueэкземпляр (клиент),context(серверная сторона) дажеstore(Vuex).

параметры функции плагина

pluginКак правило, функция выставляется извне и получает два параметра:contextиinject

контекст:Объект контекста, в котором хранится множество полезных свойств. такие как обычно используемыеappсвойства, включая все плагиныVueкорневой экземпляр. Например: с помощьюaxiosкогда ты хочешь получить$axiosнепосредственно черезcontext.app.$axiosчтобы получить.

вводить:Этот метод можетpluginодновременно вводят вcontext,Vueпример,Vuexсередина.

Например:

export default function (context, inject) {}

Внедрить экземпляр Vue

определение

plugins/vue-inject.js :

import Vue from 'vue'

Vue.prototype.$myInjectedFunction = string => console.log('This is an example', string)

использовать

nuxt.config.js :

export default {
  plugins: ['~/plugins/vue-inject.js']
}

так во всемVueЭту функцию можно использовать в любом компоненте

export default {
  mounted() {
      this.$myInjectedFunction('test')
  }
}

внедрить контекст

contextинъекционный метод и др.vueИнъекция приложений похожа.

определение

plugins/ctx-inject.js :

export default ({ app }) => {
  app.myInjectedFunction = string => console.log('Okay, another function', string)
}

использовать

nuxt.config.js :

export default {
  plugins: ['~/plugins/ctx-inject.js']
}

Теперь, пока вы получаетеcontext, вы можете использовать функцию (например, вasyncDataиfetchсередина)

export default {
  asyncData(context) {
    context.app.myInjectedFunction('ctx!')
  }
}

Одновременный впрыск

При необходимости одновременноcontext,Vueнапример, дажеVuexинъекции одновременно, вы можете использоватьinjectметод, этоpluginВторой параметр экспортируемой функции. По умолчанию система будет$в качестве префикса к имени метода.

определение

plugins/combined-inject.js :

export default ({ app }, inject) => {
  inject('myInjectedFunction', string => console.log('That was easy!', string))
}

использовать

nuxt.config.js :

export default {
  plugins: ['~/plugins/combined-inject.js']
}

Теперь вы можетеcontext,илиVueв случаеthis,илиVuexизactions / mutationsв методеthisзвонитьmyInjectedFunctionметод

export default {
  mounted() {
    this.$myInjectedFunction('works in mounted')
  },
  asyncData(context) {
    context.app.$myInjectedFunction('works with context')
  }
}

store/index.js :

export const state = () => ({
  someValue: ''
})

export const mutations = {
  changeSomeValue(state, newValue) {
    this.$myInjectedFunction('accessible in mutations')
    state.someValue = newValue
  }
}

export const actions = {
  setSomeValueToWhatever({ commit }) {
    this.$myInjectedFunction('accessible in actions')
    const newValue = 'whatever'
    commit('changeSomeValue', newValue)
  }
}

плагин звонит друг другу

когдаpluginзависит от другихpluginПри вызове мы можем получить доступcontextполучить при условии, чтоpluginНужно использоватьcontextинъекция.

Например: сейчас естьrequestпросилplugin, есть еще одноpluginнужно позвонитьrequest

plugins/request.js :

export default ({ app: { $axios } }, inject) => {
  inject('request', {
    get (url, params) {
      return $axios({
        method: 'get',
        url,
        params
      })
    }
  })
}

plugins/api.js:

export default ({ app: { $request } }, inject) => {
  inject('api', {
    getIndexList(params) {
      return $request.get('/list/indexList', params)
    }
  })
}

Стоит отметить, что при инъекцияхpluginОбращая внимание на порядок, в приведенном выше примереrequestПоследовательность впрыска должна бытьapiДо

module.exports = {
  plugins: [
    './plugins/axios.js',
    './plugins/request.js',
    './plugins/api.js',
  ]
}

конфигурация маршрутизации

существуетNuxt.js, маршруты генерируются автоматически на основе файловой структуры без настройки. Автоматически сгенерированную конфигурацию маршрутизации можно найти по адресу.nuxt/router.jsПосмотреть в.

динамическая маршрутизация

существуетVueВот как настраивается динамическая маршрутизация в

const router = new VueRouter({
  routes: [
    {
      path: '/users/:id',
      name: 'user',
      component: User
    }
  ]
})

Nuxt.jsВам нужно создать соответствующий префикс подчеркивания вVueфайл или каталог

В качестве примера возьмем следующий каталог:

pages/
--| users/
-----| _id.vue
--| index.vue

Автоматически сгенерированная конфигурация маршрутизации выглядит следующим образом:

router:{
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    }
  ]
}

Вложенные маршруты

Возьмем для примера следующий каталог, нам нужна страница первого уровняvueфайл и папку с тем же именем, что и файл (для подстраниц)

pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue

Автоматически сгенерированная конфигурация маршрутизации выглядит следующим образом:

router: {
  routes: [
    {
      path: '/users',
      component: 'pages/users.vue',
      children: [
        {
          path: '',
          component: 'pages/users/index.vue',
          name: 'users'
        },
        {
          path: ':id',
          component: 'pages/users/_id.vue',
          name: 'users-id'
        }
      ]
    }
  ]
}

Затем на странице первого уровня используйтеnuxt-childдля отображения подстраниц, таких как использованиеrouter-viewТакой же

<template>
  <div>
    <nuxt-child></nuxt-child>
  </div>
</template>

пользовательская конфигурация

Помимо создания маршрутов на основе файловой структуры, вы также можете изменитьnuxt.config.jsдокументrouterпараметры для настройки, эти конфигурации будут добавлены вNuxt.jsв конфигурации маршрутизации.

В следующем примере показана конфигурация для добавления перенаправления к маршруту:

module.exports = {
  router: {
    extendRoutes (routes, resolve) {
      routes.push({
        path: '/',
        redirect: {
          name: 'timeline-title'
        }
      })
    }
  }
}

axios

Установить

Nuxtинтегрировано для нас@nuxtjs/axios, если вы выбрали при создании проектаaxios, этот шаг можно пропустить.

npm i @nuxtjs/axios --save

nuxt.config.js :

module.exports = {
  modules: [
    '@nuxtjs/axios'
  ],
}

SSR использует Axios

Сторона сервера извлекает и отображает данные,asyncDataМетод может асинхронно извлекать данные перед визуализацией компонента и возвращать полученные данные текущему компоненту.

export default {
  async asyncData(context) {
    let data = await context.app.$axios.get("/test")
    return {
      list: data
    };
  },
  data() {
    return {
      list: []
    }
  }
}

Без SSR с использованием Axios

Этот способ использования такой же, как мы обычно делаем, посетитеthisпозвонить

export default {
  data() {
    return {
      list: []
    }
  },
  async created() {
    let data = await this.$axios.get("/test")
    this.list = data
  },
}

Пользовательская конфигурация Axios

Большую часть времени нам нужноaxiosВыполните пользовательскую настройку (baseUrl, перехватчик), затем вы можете настроитьpluginsимпортировать.

определение

/plugins/axios.js :

export default function({ app: { $axios } }) {
  $axios.defaults.baseURL = 'http://127.0.0.1:8000/'
  $axios.interceptors.request.use(config => {
    return config
  })
  $axios.interceptors.response.use(response => {
    if (/^[4|5]/.test(response.status)) {
      return Promise.reject(response.statusText)
    }
    return response.data
  })
}

использовать

nuxt.config.js :

module.exports = {
  plugins: [
    './plugins/axios.js'
  ],
}

После этого используйте тот же способ, что и выше.

css-препроцессор

отscssНапример

Установить

npm i node-sass sass-loader scss-loader --save--dev

использовать

Настройка не требуется, используется непосредственно в шаблоне

<style lang="scss" scoped>
.box{
    color: $theme;
}
</style>

глобальный стиль

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

определение

global.scss :

.shadow{
  box-shadow: 0 1px 2px 0 rgba(0,0,0,.05);
}
.ellipsis{
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.main{
  width: 960px;
  margin: 0 auto;
  margin-top: 20px;
}

использовать

nuxt.config.js :

module.exports = {
  css: [
    '~/assets/scss/global.scss'
  ],
}

глобальная переменная

вводить переменные на страницу иmixinИ вам не нужно импортировать их каждый раз, вы можете использовать@nuxtjs/style-resourcesреализовать.

Установить

npm i @nuxtjs/style-resources --save--dev

определение

/assets/scss/variable.scss:

$theme: #007fff;
$success: #6cbd45;
$success-2: #74ca46;

использовать

nuxt.config.js :

module.exports = {
  modules: [
    '@nuxtjs/style-resources'
  ],
  styleResources: {
    scss: [
      './assets/scss/variable.scss'
    ]
  },
}

пользовательская тема element-ui

определение

/assets/scss/element-variables.scss:

/* 改变主题色变量 */
/* $theme 在上面的 scss 文件中定义并使用 */
$--color-primary: $theme;

/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

/* 组件样式按需引入 */
@import "~element-ui/packages/theme-chalk/src/select";
@import "~element-ui/packages/theme-chalk/src/option";
@import "~element-ui/packages/theme-chalk/src/input";
@import "~element-ui/packages/theme-chalk/src/button";
@import "~element-ui/packages/theme-chalk/src/notification";
@import "~element-ui/packages/theme-chalk/src/message";

использовать

nuxt.config.js :

module.exports = {
  modules: [
    '@nuxtjs/style-resources'
  ],
  styleResources: {
    scss: [
      /*
      * 这里需要注意使用的顺序,因为 element-variables.scss 里用到 variable.scss 里定义的变量
      * 如果顺序反过来,在启动编译时会导致变量找不到报错
      */
      '~/assets/scss/variable.scss',
      '~/assets/scss/element-variables.scss'
    ]
  },
}

Есть еще один метод, который можно использовать, а именноplugin

import Vue from 'vue'
import myComponentsInstall from '~/components/myComponentsInstall'
import eleComponentsInstall from '~/components/eleComponentsInstall'
import '~/assets/scss/element-variables.scss' // elementUI 自定义主题色

Vue.use(myComponentsInstall)
Vue.use(eleComponentsInstall)

Передовая технологическая точка

параллельные запросы asyncData

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

export default {
  async asyncData({ app }) {
    // 文章列表
    let indexData = await app.$api.getIndexList({
      first: 20,
      order: 'POPULAR',
      category: 1
    }).then(res => res.s == 1 ? res.d : {})
    // 推荐作者
    let recommendAuthors = await app.$api.getRecommendAuthor({ 
      limit: 5
    }).then(res => res.s == 1 ? res.d : [])
    // 推荐小册
    let recommendBooks = await app.$api.getRecommendBook().then(res => res.s === 1 ? res.d.data : [])
    return {
      indexData,
      recommendAuthors,
      recommendBooks
    }
  }
}

Описанная выше операция не кажется проблемой, но на самом деле есть деталь, которую можно оптимизировать. Теперь для тарелки, мы все знаемasync/awaitАсинхронная задача будет десинхронизирована и выполнена, а следующая асинхронная задача будет находиться в состоянии ожидания до завершения предыдущей асинхронной задачи. Это требует ожидания 3 асинхронных задач, предполагая, что все эти запросы занимают 1 секунду, то есть страница будет ждать не менее 3 секунд, прежде чем появится контент. Изначально мы хотели использовать отрисовку на стороне сервера для оптимизации первого экрана, но теперь отрисовка страницы тормозится из-за ожидания запросов, что не стоит потерь.

Лучшим решением должна быть отправка нескольких запросов одновременно, может умные друзья уже додумались до этогоPromise.all. Да, используйтеPromise.allОтправка этих запросов параллельно решает указанную выше проблему.Promise.allпринять одинPromiseмассив в качестве параметра, когда всеPromiseВ случае успеха возвращается массив результатов. Последнее время будет самым длиннымPromiseТаким образом, исходные 3 секунды можно сократить до 1 секунды. Следует отметить, что если один из запросов завершится ошибкой, он вернет первыйrejectЗначение статуса сбоя, в результате которого данные не были получены. Я сделал, когда проект инкапсулирует базовый запросcatchобработка ошибок, поэтому убедитесь, что запросы неreject.

export default {
  asyncData() {
    // 数组解构获得对应请求的数据
    let [indexData, recommendAuthors, recommendBooks] = await Promise.all([
      // 文章列表
      app.$api.getIndexList({
        first: 20,
        order: 'POPULAR',
        category: 1
      }).then(res => res.s == 1 ? res.d : {}),
      // 推荐作者
      app.$api.getRecommendAuthor({ 
        limit: 5
      }).then(res => res.s == 1 ? res.d : []),
      // 推荐小册
      app.$api.getRecommendBook().then(res => res.s === 1 ? res.d.data : []),
    ])
    return {
      indexData,
      recommendAuthors,
      recommendBooks
    }
  }
}

Настройка и хранение токена

Важной особенностью приложения являетсяtokenПроверка, обычно мы сохраняем возвращенную информацию о проверке после входа в систему, а затем запрашиваем ееtokenДля статуса проверки серверной части. В проектах, где передняя и задняя части разделены, они обычно хранятся в локальном хранилище. ноNuxt.jsДругое дело, что из-за особенностей рендеринга на стороне сервера некоторые запросы инициируются на стороне сервера, и мы не можем их получитьlocalStorageилиsessionStorage.

В этот момент,cookieпригодился.cookieОн не только может работать на стороне клиента, но также будет отправлен обратно на сервер по запросу. Используйте нативные операцииcooikeочень хлопотно, с помощьюcookie-universal-nuxtМодуль (этот модуль просто помогает нам внедрять, в основном реализовывать зависимостиcookie-universal), мы можем использовать его более удобноcookie. Будь то на стороне сервера или клиента,cookie-universal-nuxtоба обеспечивают нам последовательноеapi, это поможет нам адаптировать соответствующий метод внутри компании.

Установить

Установитьcookie-universal-nuxt

npm install cookie-universal-nuxt --save

nuxt.config.js :

module.exports = {
  modules: [
    'cookie-universal-nuxt'
  ],
}

Основное использование

такой же,cookie-universal-nuxtбудет введен в то же время, доступ$cookiesиспользовать.

Сервер:

// 获取
app.$cookies.get('name')
// 设置
app.$cookies.set('name', 'value')
// 删除
app.$cookies.remove('name')

Клиент:

// 获取
this.$cookies.get('name')
// 设置
this.$cookies.set('name', 'value')
// 删除
this.$cookies.remove('name')

Нажмите здесь, чтобы узнать больше об использованииУууу, эта лошадь plus.com/package/COO…

Практический процесс подачи заявки

Как и логин Nuggets, информация о проверке будет храниться в течение длительного времени после входа в систему, вместо того, чтобы входить в систему каждый раз, когда мы ее используем. ноcookieЖизненный цикл существует только в браузере, и он будет уничтожен при закрытии браузера, поэтому нам нужно установить для него более длительный срок действия.

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

/utils/utils.js :

setAuthInfo(ctx, res) {
  let $cookies, $store
  // 客户端
  if (process.client) {
    $cookies = ctx.$cookies
    $store = ctx.$store
  }
  // 服务端
  if (process.server) {
    $cookies = ctx.app.$cookies
    $store = ctx.store
  }
  if ($cookies && $store) {
    // 过期时长 new Date(Date.now() + 8.64e7 * 365 * 10)
    const expires = $store.state.auth.cookieMaxExpires
    // 设置cookie
    $cookies.set('userId', res.userId, { expires })
    $cookies.set('clientId', res.clientId, { expires })
    $cookies.set('token', res.token, { expires })
    $cookies.set('userInfo', res.user, { expires })
    // 设置vuex
    $store.commit('auth/UPDATE_USERINFO', res.user)
    $store.commit('auth/UPDATE_CLIENTID', res.clientId)
    $store.commit('auth/UPDATE_TOKEN', res.token)
    $store.commit('auth/UPDATE_USERID', res.userId)
  }
}

нужно переделывать позжеaxios, пусть при запросе выводит информацию аутентификации:

/plugins/axios.js :

export default function ({ app: { $axios, $cookies } }) {
  $axios.defaults.baseURL = process.env.baseUrl
  $axios.defaults.timeout = 30000
  $axios.interceptors.request.use(config => {
    // 头部带上验证信息
    config.headers['X-Token'] = $cookies.get('token') || ''
    config.headers['X-Device-Id'] = $cookies.get('clientId') || ''
    config.headers['X-Uid'] = $cookies.get('userId') || ''
    return config
  })
  $axios.interceptors.response.use(response => {
    if (/^[4|5]/.test(response.status)) {
    	return Promise.reject(response.statusText)
    }
    return response.data
  })
}

Промежуточное ПО для проверки разрешений

Как было сказано выше, информация об удостоверении личности будет устанавливаться долго, а затем, конечно, необходимо проверить, не истек ли срок действия удостоверения. Здесь я буду использовать промежуточное программное обеспечение маршрутизации для завершения функции проверки.Промежуточное программное обеспечение запускается до того, как страница или группа страниц будут отрисованы, точно так же, как защита маршрутизации. И каждое промежуточное ПО должно быть помещено вmiddlewareИмя каталога, имя файла станет именем промежуточного программного обеспечения. Промежуточное ПО может выполняться асинхронно, просто вернитесьPromiseВот и все.

определение

/middleware/auth.js:

export default function (context) {
  const { app, store } = context
  const cookiesToken = app.$cookies.get('token')
  if (cookiesToken) {
    // 每次跳转路由 验证登录状态是否过期
    return app.$api.isAuth().then(res => {
      if (res.s === 1) {
        if (res.d.isExpired) {   // 过期 移除登陆验证信息
          app.$utils.removeAuthInfo(context)
        } else {                 // 未过期 重新设置存储
          const stateToken = store.state.auth.token
          if (cookiesToken && stateToken === '') {
            store.commit('auth/UPDATE_USERINFO', app.$cookies.get('userInfo'))
            store.commit('auth/UPDATE_USERID', app.$cookies.get('userId'))
            store.commit('auth/UPDATE_CLIENTID', app.$cookies.get('clientId'))
            store.commit('auth/UPDATE_TOKEN', app.$cookies.get('token'))
          }
        }
      }
    })
  }
}

вышеif (cookiesToken && stateToken === '')Обработка в , потому что на некоторых страницах будут открываться новые вкладки, в результате чегоvuexИнформация в утеряна, тут надо судить и сбрасывать дерево состояний.

использовать

nuxt.config.js :

module.exports = {
  router: {
    middleware: ['auth']
  },
}

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

export default {
  middleware: 'auth'
}

Документация по промежуточному ПО для маршрутизации нажмите здесьWoohoo. Злость бьет быстрее. Талант/дорого/физически...

Управление регистрацией компонентов

Сначала возьмем простейший пример, вpluginsсоздать папкуvue-global.jsИспользуется для управления компонентами или методами, которые необходимо использовать глобально:

import Vue from 'vue'
import utils from '~/utils'
import myComponent from '~/components/myComponent.vue'

Vue.prototype.$utils = utils

Vue.use(myComponent)

nuxt.config.js:

module.exports = {
  plugins: [
    '~/plugins/vue-global.js'
  ],
}

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

Для некоторых пользовательских глобальных общих компонентов мой подход заключается в том, чтобы поместить их в/components/commonЕдиное управление папками. Это можно использоватьrequire.contextДля автоматизации введения компонентов предусмотрен методwebpackПри условии, что он может читать все файлы в папке. Если вы не знаете этот метод, очень хорошо, что вы его понимаете и используете, это может значительно повысить эффективность вашего программирования.

определение

/components/myComponentsInstall.js :

export default {
  install(Vue) {
    const components = require.context('~/components/common', false, /\.vue$/)
    // components.keys() 获取文件名数组
    components.keys().map(path => {
      // 获取组件文件名
      const fileName = path.replace(/(.*\/)*([^.]+).*/ig, "$2")
      // components(path).default 获取 ES6 规范暴露的内容,components(path) 获取 Common.js 规范暴露的内容
      Vue.component(fileName, components(path).default || components(path))
    })
  } 
}

использовать

/plugins/vue-global.js :

import Vue from 'vue'
import myComponentsInstall from '~/components/myComponentsInstall'

Vue.use(myComponentsInstall)

После вышеперечисленных операций компонент был зарегистрирован глобально, нам осталось только назвать его через тире. И каждый новый компонент не нужно вводить заново, это действительно делается один раз и навсегда. Также и в других практических приложениях, еслиapiФайл разделен на модули по функциям, и этот метод также можно использовать для автоматического импорта интерфейсных файлов.

Библиотека сторонних компонентов (элементный пользовательский интерфейс)

импортировать все

/plugins/vue-global.js:

import Vue from 'vue'
import elementUI from 'element-ui'

Vue.use(elementUI)

nuxt.config.js :

module.exports = {
  css: [
    'element-ui/lib/theme-chalk/index.css'
  ]
}

Внедрить по требованию

с помощьюbabel-plugin-component, мы можем только ввести необходимые компоненты для достижения цели уменьшения размера проекта.

npm install babel-plugin-component -D

nuxt.config.js :

module.exports = {
  build: {
    plugins: [
      [
        "component",
        {
          "libraryName": "element-ui",
          "styleLibraryName": "theme-chalk"
        }
      ]
    ],
  }
}

Далее вводим некоторые из необходимых нам компонентов, а также создаемeleComponentsInstall.jsКомпоненты, управляющие elementUI:

/components/eleComponentsInstall.js :

import { Input, Button, Select, Option, Notification, Message } from 'element-ui'

export default {
  install(Vue) {
    Vue.use(Input)
    Vue.use(Select)
    Vue.use(Option)
    Vue.use(Button)
    Vue.prototype.$message = Message
    Vue.prototype.$notify  = Notification
  }
}

/plugins/vue-global.js:

import Vue from 'vue'
import eleComponentsInstall from '~/components/eleComponentsInstall'

Vue.use(eleComponentsInstall)

переключатель макета страницы

Когда мы создаем веб-приложения, макет большинства страниц остается неизменным. Однако в некоторых случаях может потребоваться замена другого метода макета.layoutПараметры конфигурации могут помочь нам в этом. И каждый файл макета должен быть помещен вlayoutsкаталог, имя файла будет именем макета, макет по умолчаниюdefault. В следующем примере показано изменение цвета фона макета страницы. На самом деле, судя по использованиюVueпонимание, это похоже на переключениеApp.vue.

определение

/layouts/default.vue :

<template>
  <div style="background-color: #f4f4f4;min-height: 100vh;">
    <top-bar></top-bar>
    <main class="main">
      <nuxt />
    </main>
    <back-top></back-top>
  </div>
</template>

/layouts/default-white.vue :

<template>
  <div style="background-color: #ffffff;min-height: 100vh;">
    <top-bar></top-bar>
    <main class="main">
      <nuxt />
    </main>
    <back-top></back-top>
  </div>
</template>

использовать

Файл компонента страницы:

export default {
  layout: 'default-white',
  // 或
  layout(context) {
    return 'default-white'
  }
}

пользовательская страница ошибки

Пользовательские страницы ошибок должны быть размещеныlayoutsкаталог, а имя файлаerror. Хотя этот файл находится вlayoutsкаталог, но его следует рассматривать как страницу. Этот файл макета не должен содержать<nuxt/>Этикетка. Вы можете думать об этом файле макета как о компоненте, который отображает ошибки приложения (404, 500 и т. д.).

определение

<template>
  <div class="error-page">
    <div class="error">
      <div class="where-is-panfish">
        <img class="elem bg" src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/v3/static/img/bg.1f516b3.png~tplv-t2oaga2asx-image.image">
        <img class="elem panfish" src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/v3/static/img/panfish.9be67f5.png~tplv-t2oaga2asx-image.image">
        <img class="elem sea" src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/v3/static/img/sea.892cf5d.png~tplv-t2oaga2asx-image.image">
        <img class="elem spray" src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/v3/static/img/spray.bc638d2.png~tplv-t2oaga2asx-image.image">
      </div>
      <div class="title">{{statusCode}} - {{ message }}</div>
      <nuxt-link class="error-link" to="/">回到首页</nuxt-link>
    </div>
  </div>
</template>
export default {
  props: {
    error: {
      type: Object,
      default: null
    }
  },
  computed: {
    statusCode () {
      return (this.error && this.error.statusCode) || 500
    },
    message () {
      return this.error.message || 'Error'
    }
  },
  head () {
    return {
      title: `${this.statusCode === 404 ? '找不到页面' : '呈现页面出错'} - 掘金`,
      meta: [
        {
          name: 'viewport',
          content: 'width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no'
        }
      ]
    }
  }
}

объект ошибки

на странице ошибкиpropsпринять одинerrorобъект, который содержит по крайней мере два свойстваstatusCodeиmessage.

Помимо этих двух атрибутов, мы можем передавать и другие атрибуты, и здесь речь пойдет об упомянутых вышеerrorметод:

export default {
  async asyncData({ app, query, error }) {
    const tagInfo = await app.$api.getTagDetail({
      tagName: encodeURIComponent(query.name)
    }).then(res => {
      if (res.s === 1) {
        return res.d
      } else {
        // 这样我们在 error 对象中又多了 query 属性
        error({
          statusCode: 404,
          message: '标签不存在',
          query 
        })
        return
      }
    })
    return {
      tagInfo
    }
  }
}

Есть также страницыvalidateжизненный цикл:

export default {
  async validate ({ params, store }) {
    throw new Error('页面参数不正确')
  }
}

прошел здесьstatusCode500,messageэтоnew Errorсодержание в . Если вы хотите передать объект в прошлое,messageбудет преобразовано в строку[object Object],ты можешь использоватьJSON.stringifyПередайте его в прошлом, а затем обработайте страницу с ошибкой и проанализируйте ее.

export default {
  async validate ({ params, store }) {
    throw new Error(JSON.stringify({ 
      message: 'validate错误',
      params
    }))
  }
}

Инкапсулировать нижнее событие

В основном каждая страница в проекте будет иметь нижнее событие, поэтому я извлек эту логику вmixin, нужную страницу можно импортировать и использовать.

/mixins/reachBottom.js :

export default {
  data() {
    return {
      _scrollingElement: null,
      _isReachBottom: false,  // 防止进入执行区域时 重复触发
      reachBottomDistance: 80 // 距离底部多远触发
    }
  },
  mounted() {
    this._scrollingElement = document.scrollingElement
    window.addEventListener('scroll', this._windowScrollHandler)
  },
  beforeDestroy() {
    window.removeEventListener('scroll', this._windowScrollHandler)
  },
  methods: {
    _windowScrollHandler() {
      let scrollHeight = this._scrollingElement.scrollHeight
      let currentHeight = this._scrollingElement.scrollTop + this._scrollingElement.clientHeight + this.reachBottomDistance
      if (currentHeight < scrollHeight && this._isReachBottom) {
        this._isReachBottom = false
      }
      if (this._isReachBottom) {
        return
      }
      // 触底事件触发
      if (currentHeight >= scrollHeight) {
        this._isReachBottom = true
        typeof this.reachBottom === 'function' && this.reachBottom()
      }
    }
  },
}

Основой реализации, конечно же, является синхронизация триггера:scrollTop(расстояние прокрутки страницы)+clientHeight(видимая высота страницы) >=scrollHeight(Общая высота страницы, включая область прокрутки). Но это должно быть полностью достигнуто, чтобы вызвать событие, поэтому, кроме того, я добавляюreachBottomDistanceИспользуется для управления расстоянием, на котором срабатывает событие. В конце концов, инициирующее событие вызывает страницуmethodsизreachBottomметод.

Императивный всплывающий компонент

Что такое императивные компоненты?element-UIизMessageХорошим примером являются компоненты.Когда нам нужны всплывающие подсказки, нам нужно только вызватьthis.message(), вместо прохожденияv-ifКомпоненты переключения. Преимущество этого в том, что нет необходимости вводить компоненты, удобно пользоваться, а где нужно настраивать.

nuxt-juejin-projectВ проекте я также инкапсулировал два общих компонента всплывающих окон, всплывающее окно входа в систему и всплывающее окно предварительного просмотра.Технический момент заключается в ручном монтировании компонентов. Кода реализации не много, достаточно нескольких строчек.

определение

/components/common/picturesModal/picturesModal.vue :

export default {
  data() {
    return {
      url: '',  // 当前图片链接
      urls: ''  // 图片链接数组
    }
  },
  methods: {
    show(cb) {
      this.cb = cb
      return new Promise((resolve, reject) => {
        document.body.style.overflow = 'hidden'
        this.resolve = resolve
        this.reject = reject
      })
    },
    // 销毁弹窗
    hideModal() {
      typeof this.cb === 'function' && this.cb()
      document.body.removeChild(this.$el)
      document.body.style.overflow = ''
      // 销毁组件实例
      this.$destroy()
    },
    // 关闭弹窗
    cancel() {
      this.reject()
      this.hideModal()
    },
  }
}

/components/common/picturesModal/index.js

import Vue from 'vue'
import picturesModal from './picturesModal'

let componentInstance = null

// 构造子类
let ModalConstructor = Vue.extend(picturesModal)

function createModal(options) {
  // 实例化组件
  componentInstance = new ModalConstructor()
  // 合并选项
  Object.assign(componentInstance, options)
  // $mount可以传入选择器字符串,表示挂载到该选择器
  // 如果不传入选择器,将渲染为文档之外的的元素,你可以想象成 document.createElement()在内存中生成dom
  // $el获取的是dom元素
  document.body.appendChild(componentInstance.$mount().$el)
}

function caller (options) {
  // 单例 全局只存在一个弹窗
  if (!componentInstance) {
    createModal(options)
    // 调用组件内的show方法 传入的callback在组件销毁时调用
    return componentInstance.show(() => { componentInstance = null })
  }
}

export default {
  install(Vue) {
    // 注册调起弹窗方法,方法返回Promise  then为登录成功  catch为关闭弹窗
    Vue.prototype.$picturesModal = caller
  }
}

использовать

/plugins/vue-global.js:

import picturesModal from '~/components/common/picturesModal'

Vue.use(picturesModal)

Объект, прошедший вот вышеперечисленноеcreateModalполучилаoptionsпараметры и, наконец, объединены, чтобы покрыть компонентыdata.

this.$picturesModal({
  url: 'b.jpg'
  urls: ['a.jpg', 'b.jpg', 'c.jpg']
})

Технология среднего уровня

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

  • Прокси: в среде разработки мы можем использовать прокси для решения наиболее распространенных междоменных проблем; в онлайн-среде мы можем использовать прокси для пересылки запросов на несколько серверов.
  • Кэширование: Кеширование на самом деле является требованием ближе к внешнему интерфейсу. Действия пользователя запускают обновление данных, а средний уровень узла может напрямую обрабатывать некоторые требования к кэшированию.
  • Журнал: по сравнению с другими серверными языками запись журнала на среднем уровне узла позволяет более удобно и быстро обнаруживать проблемы.
  • Мониторинг: хорошо справляется с высокой параллельной обработкой запросов, мониторинг также является подходящим вариантом.
  • Обработка данных: возврат необходимых данных, псевдонимы полей данных, агрегация данных.

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

nuxt-juejin-projectСредний слой проекта используетkoaрама, промежуточнаяhttpМетод запроса основан наrequestБиблиотека просто инкапсулирована, а код реализован на/server/request/index.js. Поскольку его нужно использовать позже, я упомяну его здесь.

запрос на переадресацию

Установите соответствующее промежуточное ПО

npm i koa-router koa-bodyparser --save

koa-router: промежуточное ПО маршрутизатора, которое может быстро определять маршруты и управлять маршрутами.

koa-bodyparser: ПО промежуточного слоя для синтаксического анализа параметров, поддерживает синтаксический анализ json, типов форм и часто используется для анализа запросов POST.

Использование связанного промежуточного программного обеспечения находится вnpmЧто касается поиска, я не буду вдаваться в подробности того, как его использовать здесь.

дизайн маршрутизации

Как говорится, нет ни правил, ни квадрата.За спецификацией проектирования маршрутизации я обращаюсь к г-ну Жуану Ифэну.Руководство по проектированию RESTful API.

каталог маршрутизации

Я буду хранить файл маршрутизации в/server/routesкаталог, согласно спецификации также требует предоставленияapiПапка для номеров версий. Окончательный файл маршрутизации хранится в/server/routes/v1середина.

путь маршрутизации

В архитектуре RESTful каждый URL-адрес представляет собой ресурс (resource), поэтому в URL-адресе не может быть глаголов, только существительные, а используемые существительные часто соответствуют именам таблиц базы данных. Как правило, таблицы в базе данных представляют собой «наборы» записей одного типа, поэтому существительные в API также должны стоять во множественном числе.

Например:

  • Файлы интерфейса, связанные со статьей, называютсяarticles
  • Файл интерфейса, связанный с тегом, называетсяtag
  • Файлы интерфейса, зависящие от температуры кипения, называютсяpins

тип маршрута

Конкретный тип ресурса операции маршрута, заданный параметромHTTPпредставление глагола

  • ПОЛУЧИТЬ (ВЫБРАТЬ): получить ресурсы с сервера.
  • POST (CREATE): Создайте новый ресурс на сервере.
  • PUT (UPDATE): обновить ресурс на сервере (клиент предоставляет полный ресурс после изменения).
  • УДАЛИТЬ (DELETE): Удаляет ресурс с сервера.

логика маршрутизации

Ниже приведен пример интерфейса списка пользовательских столбцов.

/server/routes/articles.js

const Router = require('koa-router')
const router = new Router()
const request = require('../../request')
const { toObject } = require('../../../utils')

/**
 * 获取用户专栏文章
 * @param {string} targetUid - 用户id
 * @param {string} before - 最后一条的createdAt,下一页传入
 * @param {number} limit - 条数
 * @param {string} order - rankIndex:热门、createdAt:最新
 */
router.get('/userPost', async (ctx, next) => {
  // 头部信息
  const headers = ctx.headers
  const options = {
    url: 'https://timeline-merger-ms.juejin.im/v1/get_entry_by_self',
    method: "GET",
    params: {
      src: "web",
      uid: headers['x-uid'],
      device_id: headers['x-device-id'],
      token: headers['x-token'],
      targetUid: ctx.query.targetUid,
      type: ctx.query.type || 'post',
      limit: ctx.query.limit || 20,
      before: ctx.query.before,
      order: ctx.query.order || 'createdAt'
    }
  };
  // 发起请求
  let { body } = await request(options)
  // 请求后获取到的数据为 json,需要转为 object 进行操作
  body = toObject(body)
  ctx.body = {
    s: body.s,
    d: body.d.entrylist || []
  }
})

module.exports = router

зарегистрировать маршрут

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

/server/index.js :

const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
const router = new Router()

// 使用中间件
function useMiddleware(){
  app.use(bodyParser())
}

// 注册路由
function useRouter(){
  let module = require('./routes/articles')
  router.use('/v1/articles', module.routes())
  app.use(router.routes()).use(router.allowedMethods())
}

function start () {
  useMiddleware()
  useRouter()
  app.listen(8000, '127.0.0.1')
}

start()

Адрес вызова конечного интерфейса:http://127.0.0.1:8000/v1/articles/userPost

Регистрация автоматизации маршрутизации

Да, это снова здесь. Автоматизация благоухает, может ли она благоухать раз и навсегда?

const fs = require('fs')
const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
const router = new Router()

// 注册路由
function useRouter(path){
  path = path || __dirname + '/routes'
  // 获取 routes 目录下的所有文件名,urls为文件名数组
  let urls = fs.readdirSync(path)
  urls.forEach((element) => {
    const elementPath = path + '/' + element
    const stat = fs.lstatSync(elementPath);
    // 是否为文件夹
    const isDir = stat.isDirectory();
    // 文件夹递归注册路由
    if (isDir) {
      useRouter(elementPath)
    } else {
      let module = require(elementPath)
      let routeRrefix = path.split('/routes')[1] || ''
      //routes里的文件名作为 路由名
      router.use(routeRrefix + '/' + element.replace('.js', ''), module.routes())
    }
  })
  //使用路由
  app.use(router.routes()).use(router.allowedMethods())
}

function start () {
  useMiddleware()
  useRouter()
  app.listen(8000, '127.0.0.1')
}

start()

Код выше начинается сroutesВ качестве домашнего каталога маршрута посмотрите внизjsмаршрут регистрации файла, который заканчиваетсяjsпуть к файлу как имя маршрута. Например,/server/routes/v1/articles.jsЕсть поисковый интерфейс/search, то адрес вызова интерфейсаlocalhost:8000/v1/articles/search.

Проверка параметров маршрутизации

Проверка параметров является обязательной в интерфейсе, а неправильные параметры вызовут непредвиденные ошибки в программе. Мы должны заранее проверить параметры, прервать неверный запрос и сообщить об этом пользователю. В проекте, на котором я основанasync-validatorИнкапсулирует промежуточное ПО маршрутизации для проверки параметров. если ты не знаешьkoaДля рабочего процесса промежуточного программного обеспечения необходимо понимать луковую модель.

определение

/server/middleware/validator/js :

const { default: Schema } = require('async-validator')

module.exports = function (descriptor) {
  return async function (ctx, next) {
    let validator = new Schema(descriptor)
    let params = {}
    // 获取参数
    Object.keys(descriptor).forEach(key => {
      if (ctx.method === 'GET') {
        params[key] = ctx.query[key]
      } else if (
        ctx.method === 'POST' ||
        ctx.method === 'PUT' ||
        ctx.method === 'DELETE'
      ) {
        params[key] = ctx.request.body[key]
      }
    })
    // 验证参数
    const errors = await validator.validate(params)
      .then(() => null)
      .catch(err => err.errors)
    // 如果验证不通过 则返回错误
    if (errors) {
      ctx.body = {
        s: 0,
        errors
      }
    } else {
      await next()
    }
  }
}

использовать

Пожалуйста, обратитесь к тому, как использоватьasync-validator

const Router = require('koa-router')
const router = new Router()
const request = require('../../request')
const validator = require('../../middleware/validator')
const { toObject } = require('../../../utils')

/**
 * 获取用户专栏文章
 * @param {string} targetUid - 用户id
 * @param {string} before - 最后一条的createdAt,下一页传入
 * @param {number} limit - 条数
 * @param {string} order - rankIndex:热门、createdAt:最新
 */
router.get('/userPost', validator({
  targetUid: { type: 'string', required: true },
  before: { type: 'string' },
  limit: { 
    type: 'string', 
    required: true,
    validator: (rule, value) => Number(value) > 0,
    message: 'limit 需传入正整数'
  },
  order: { type: 'enum', enum: ['rankIndex', 'createdAt'] }
}), async (ctx, next) => {
  const headers = ctx.headers
  const options = {
    url: 'https://timeline-merger-ms.juejin.im/v1/get_entry_by_self',
    method: "GET",
    params: {
      src: "web",
      uid: headers['x-uid'],
      device_id: headers['x-device-id'],
      token: headers['x-token'],
      targetUid: ctx.query.targetUid,
      type: ctx.query.type || 'post',
      limit: ctx.query.limit || 20,
      before: ctx.query.before,
      order: ctx.query.order || 'createdAt'
    }
  };
  let { body } = await request(options)
  body = toObject(body)
  ctx.body = {
    s: body.s,
    d: body.d.entrylist || []
  }
})

module.exports = router

typeпредставляет тип параметра,requiredТребуется делегат. когдаtypeзаenum(перечисление), значение параметра может быть толькоenumэлемент в массиве.

должны знать о том,numberТип здесь не поддается проверке, потому что параметр при передаче преобразуется в строковый тип. но мы можем пройтиvalidatorметод для настройки правил проверки, как указано вышеlimitпараметр.

Следующее, когдаlimitЧто возвращает интерфейс, когда параметр неверен:

безопасность сайта

cors

настраиватьcorsЧтобы проверить легитимность запроса, вы можете улучшить безопасность своего веб-сайта. с помощьюkoa2-corsЭто может помочь нам сделать это более легко.koa2-corsИсходников не много.Рекомендую посмотреть.Пока есть хоть какие-то базовые знания,разобраться можно.Вы должны не только уметь им пользоваться,но и знать процесс реализации.

Установить

npm install koa2-cors --save

использовать

/server/index.js :

const cors = require('koa2-cors')

function useMiddleware(){
  app.use(helmet())
  app.use(bodyParser())
  //设置全局返回头
  app.use(cors({
    // 允许跨域的域名
    origin: function(ctx) {
      return 'http://localhost:8000';
    },
    exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
    maxAge: 86400,
    // 允许携带头部验证信息
    credentials: true,  
    // 允许的方法
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'],
    // 允许的标头
    allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Token', 'X-Device-Id', 'X-Uid'],
  }))
}

Если он не соответствует тому, как был сделан запрос, или с недопустимым заголовком. При отправке запроса произойдет прямой сбой, и браузер выдастcorsОшибка в ограничении политики. Вот пример с недопустимой ошибкой заголовка:

koa-helmet

koa-helmetПредоставляет важные заголовки безопасности, чтобы сделать ваше приложение более безопасным по умолчанию.

Установить

npm install koa-helmet --save

использовать

const helmet = require('koa-helmet')

function useMiddleware(){
  app.use(helmet())
  // .....
}

По умолчанию для нас сделаны следующие настройки безопасности:

  • X-DNS-Prefetch-Control: отключитьDNSПредварительная загрузка.
  • X-Frame-Options: защита от кликджекинга.
  • X-Powered-By: удаленоX-Powered-Byзаголовки, из-за чего злоумышленникам сложнее увидеть методы, которые делают веб-сайт потенциально скомпрометированным.
  • Strict-Transport-Security: разрешите вашим пользователям использоватьHTTPS.
  • X-Download-Options: ПредотвратитьInternet ExplorerЗагрузка выполняется в контексте вашего сайта.
  • X-Content-Type-Options: установлено значениеnosniff, что помогает предотвратить попытки браузера угадать ("обнюхать")MIMEтип, который может представлять угрозу безопасности.
  • X-XSS-защита: защита от бликовXSSатака.

Дополнительные инструкции и настройки нажмите здесьУуууххххххххххххххххххххххххххххххххххх это и это лошадь плюс .com/package/

Наконец

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

Если у вас есть какие-либо предложения или улучшения, пожалуйста, дайте мне знать~

😄 Разве вы не видите здесь звездочку?GitHub.com/Chan wah fun G…

использованная литература

  • Другие часто задаваемые вопросы:www.nuxtjs.cn/faq

  • Официальная документация на гитхабе:GitHub.com/anger-fading/docs/he…(Есть исчерпывающая конфигурация и примеры использования, некоторые из которых не упомянуты в документе Nuxt.js, рекомендуется его прочитать)