предисловие
Как 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
Переменная:
- пройти через
process.env.baseUrl
- пройти через
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('页面参数不正确')
}
}
прошел здесьstatusCode
500,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, рекомендуется его прочитать)