筆記來自
Github實例
框架原理
- 把Vue原本的dev開發模式改成Nuxt
- 修改Vue的代碼,讓Vue的mount前的生命周期在服務器端完成
- 這樣改造后把本地的整個項目的代碼包括node_modules一起移到服務器啟動就行
遷移步驟
- 按官網的步驟來就行
// npx命令安裝了Node就自帶了
npx create-nuxt-app <項目名>
// 選擇NodeJS框架 Koa
// 選擇UI框架
// 選擇測試框架
// 完成
- 配置文件
// nuxt.config.js
// 沒有vue.config.js了
// 這個文件用來配置服務器的端口
// 渲染html頁面的head標簽數據,link,script,就是全局的css和js,都是用cdn地址
// 還有css和plugins,注意文件路徑
module.exports = {
mode: 'universal',
server: {
port: 8000,
host: '127.0.0.1'
},
render: {
csp: true
},
// axios的請求前綴,跟上面的port一致
env: {
baseUrl: 'http://127.0.0.1:8000'
},
router: {
middleware: ['auth', 'i18n'],
extendRoutes (routes, resolve) {
routes.push({
path: '/',
redirect: {
name: 'timeline-title'
}
})
}
},
head: {
title: 'title',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
script: []
},
css: [
'~/assets/css/reset.css',
'~/assets/css/page-transition.css',
'~/assets/scss/global.scss'
],
plugins: [
'~/plugins/axios.js',
'~/plugins/request.js',
'~/plugins/api.js',
'~/plugins/vue-global.js'
],
modules: [
'@nuxtjs/axios',
'@nuxtjs/style-resources',
'cookie-universal-nuxt'
],
axios: {
},
build: {
babel: {
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
],
},
extend (config, ctx) {
}
}
}
- Nuxt的路由
// 把 <router-link to="/">首頁</router-link> 改成
<nuxt-link to="/">首頁</nuxt-link>
// 用$router跳轉的就不用改了
// Nuxt是沒有vue-router的
// 他的路由是用文件路徑生成的,比如
pages/
--| _slug/
-----| index.vue
--| 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'
},
{
name: 'slug',
path: '/:slug',
component: 'pages/_slug/index.vue'
}
]
}
- Nuxt的app.vue
// 以前的app.vue是 <router-view />
// 現在是在layouts/xx.vue自定義的
// 默認有一個叫default.vue
// 內容顯示在 <nuxt /> 里
// 如果文件里沒有error.vue,需要自己寫一個,就隨便寫一個,寫上404就行
// 那指定哪個layout在哪配置呢,往下看
- Vue文件的修改
// 在原來的Vue的屬性和方法的基礎上,添加了下面的熟悉和方法
// 這些方法是用在渲染的時候的
// asyncData,最重要的一個鍵, 支持 異步數據處理,另外該方法的第一個參數為當前頁面組件的 上下文對象。
// head,配置當前頁面的 Meta 標簽, 詳情參考 頁面頭部配置API。
// layout,指定當前頁面使用的布局(layouts 根目錄下的布局文件)。詳情請參考 關於 布局 的文檔。
// loading,如果設置為false,則阻止頁面自動調用加載提示
// transition,指定頁面切換的過渡動效, 詳情請參考 頁面過渡動效。
// scrollToTop,布爾值,默認: false。 用於判定渲染頁面前是否需要將當前頁面滾動至頂部。這個配置用於 嵌套路由的應用場景。
// validate,校驗方法用於校驗 動態路由的參數。
// middleware,指定頁面的中間件,中間件會在頁面渲染之前被調用, 請參考 路由中間件
// 放一個例子
// 這個方法需要主動調用,等於是自定義生命周期函數,而且必須是這個名字
// 這個方法代替了vue的 beforeCreate 和 created 兩個生命周期
// mounted 還是在客戶端執行,mounted不應該寫數據請求,防止重復請求
// 結構參數,app是this實例,params是請求的url,store是vuex實例
async asyncData({ app, params, store }) {
// 可以在asyncData獲取和修改store
// 往下看可以發現這個categoryList的數據是來自validate的
let categoryList = store.state.category.recommendCategoryList
categoryList[0].name = "text";
store.commit('category/UPDATE_TIMELINE_CATEGORY_LIST', categoryList)
// 作者榜單
let res = await app.$api.getAuthorRank({
channel: params.name,
after: '',
first: 20
}).then(res => res.s === 1 ? res.d : {})
return {
categoryList,
authors: res.edges,
pageInfo: res.pageInfo
}
},
head() {
return {
title: this.pageInfo.name || 'xxx'
}
},
async validate ({ app, params, store }) {
// validate是執行在asyncData之前的
// 可以在validate獲取和修改store
if (params.id && params.id != 'undefined') {
let categoryList = []
// 獲取分類列表緩存
if (store.state.category.timelineCategoryList.length) {
categoryList = store.state.category.timelineCategoryList
} else {
categoryList = await app.$api.getCategories().then(res => res.s === 1 ? initCategoryList.concat(res.d.categoryList) : initCategoryList)
store.commit('category/UPDATE_TIMELINE_CATEGORY_LIST', categoryList)
}
return true
}
return false
},
// 下面就是原Vue的老代碼
data() {
return {
pinDetail: {},
page: 1,
comments: []
}
},
...
- 上面我們使用了vuex
// 但是為什么在nuxt.config.js的plugins里沒有看到引入
// 因為nuxt自帶了,只要項目里有個store的文件夾
// 放入js就行,js里是函數型的state 和 同步的mutations
// text.js
export const state = () => ({
isTopbarBlock: true, // 頂部欄是否顯示
})
export const mutations = {
UPDATE_TOPBAR_BLOCK(state, payload){
state.isTopbarBlock = payload
}
}
// 在page的validate里用commit執行
store.commit('文件名/UPDATE_TOPBAR_BLOCK', 參數)
- 請求三人組
// Nuxt不能用正常的axios,一定要用@nuxtjs/axios,他默認讓vue.$axios = axios
// 還有cookie-universal-nuxt插件,這個插件默認讓vue.$cookie = 請求用戶的cookie
// plugins/axios.js,對axios進行配置
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
})
}
// plugins/request.js
export default ({ app: { $axios } }, inject) => {
let requestList = {}
let methods = ['get', 'post', 'put', 'delete']
methods.forEach(method => {
let dataKey = method === 'get' ? 'params' : 'data'
requestList[method] = function(url, data) {
return $axios({
method,
url,
[dataKey]: data
}).catch(err => {
console.error(err)
return {
s: 0,
d: {},
errors: [err]
}
})
}
})
inject('request', requestList)
}
// plugins/api.js
export default ({ app: { $request } }, inject) => {
inject('api', {
/**
* 登錄驗證
* @param {string} password - 密碼
* @param {string} phoneNumber - 手機號碼
*/
loginAuth(data) {
return $request.post('/v1/auth/login', data)
},
/**
* 身份驗證
*/
isAuth() {
return $request.get('/v1/auth/authentication')
}
...
})
}
- Vue全局設置
// plugin/vue.global.js
// 在這里配置一些組件和過濾器
import Vue from 'vue'
import xxx from '~/components/xxx'
import utils from '~/utils/utils'
Vue.use(xxx)
Vue.filter('formatTime', d => utils.formatTime(d))
export default function (context, inject) {
inject('utils', utils)
}
- 服務器搭建,這里用Koa
// server/index.js
const fs = require('fs')
const Koa = require('koa')
const cors = require('koa2-cors')
const helmet = require('koa-helmet')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const app = new Koa()
const router = new Router()
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = app.env !== 'production'
function useMiddleware(){
app.use(helmet())
app.use(bodyParser())
//設置全局返回頭
app.use(cors({
origin: function(ctx) {
return 'http://localhost:8000'; //cors
},
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'],
}))
}
function useRouter(path){
// 路由就是接口,這里怎么配置就不寫了
}
async function start () {
// Instantiate nuxt.js
const nuxt = new Nuxt(config)
const {
host = process.env.HOST || '127.0.0.1',
port = process.env.PORT || 3000
} = nuxt.options.server
await nuxt.ready()
// Build in development
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
useMiddleware()
useRouter()
app.use((ctx) => {
ctx.status = 200
ctx.respond = false // Bypass Koa's built-in response handling
ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
nuxt.render(ctx.req, ctx.res)
})
app.listen(port, host)
}
start()
- 到這里就大致都改好了,看看Nuxt的命令有哪些
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon --inspect server/index.js --watch server",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
},
- 執行
npm run dev
,會打包出一個.nuxt
文件,順便打開服務器,直接訪問就行,如果要放到服務器,就把所有的代碼移到服務器,同樣的方法啟動就行
研究一下.nuxt文件
// router.js,這個就是上面說到的自動生成的路由文件
// App.js,就是讀取layouts文件夾生成出來的模板文件
// index.js,這個非常值得研究
// index.js把nuxt.config.js里的配置讀取后生成出來的
// 最開始可以看到很多的plugins
// 然后引入可很多的component
// 還有一個inject方法,這個方法在上面的請求三人組里可以看到很多次,就是在這里調用的,是一個固定寫法,用來給Vue實例注冊$xx屬性的,看代碼
const inject = function (key, value) {
if (!key) {
throw new Error('inject(key, value) has no key provided')
}
if (value === undefined) {
throw new Error(`inject('${key}', value) has no value provided`)
}
key = '$' + key
// Add into app
app[key] = value
}
// server.js,這個就是服務器渲染的核心
// 在這里把請求的路徑解析后,知道請求的是哪個頁面的路由,然后讀取文件,看代碼
export default async (ssrContext) => {
ssrContext.redirected = false
ssrContext.next = createNext(ssrContext)
ssrContext.beforeRenderFns = []
ssrContext.nuxt = { layout: 'default', data: [], fetch: [], error: null, state: null, serverRendered: true, routePath: '' }
const { app, router, store } = await createApp(ssrContext)
const _app = new Vue(app)
ssrContext.nuxt.routePath = app.context.route.path
ssrContext.meta = _app.$meta()
ssrContext.asyncData = {}
const beforeRender = async () => {
await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt })))
ssrContext.rendered = () => {
ssrContext.nuxt.state = store.state
}
}
/*
** Set layout
*/
let layout = Components.length ? Components[0].options.layout : NuxtError.layout
if (typeof layout === 'function') {
layout = layout(app.context)
}
await _app.loadLayout(layout)
if (ssrContext.nuxt.error) {
return renderErrorPage()
}
layout = _app.setLayout(layout)
ssrContext.nuxt.layout = _app.layoutName
// asyncDatas非常的核心
const asyncDatas = await Promise.all(Components.map((Component) => {
const promises = []
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
const promise = promisify(Component.options.asyncData, app.context)
promise.then((asyncDataResult) => {
ssrContext.asyncData[Component.cid] = asyncDataResult
applyAsyncData(Component)
return asyncDataResult
})
promises.push(promise)
} else {
promises.push(null)
}
// Call fetch(context)
if (Component.options.fetch && Component.options.fetch.length) {
promises.push(Component.options.fetch(app.context))
} else {
promises.push(null)
}
return Promise.all(promises)
}))
await beforeRender()
return _app
}
如果有必須使用的js,但不需要在服務端使用的js
- 比如mavon-editor
// nuxt.config.js 中添加plugins配置
plugins: [
...
{ src: '@/plugins/vue-mavon-editor', ssr: false }
],
// 在頁面或者組件中引入套上<no-ssr>
<template>
<div class="mavonEditor">
<no-ssr>
<mavon-editor :toolbars="markdownOption" v-model="handbook"/>
</no-ssr>
</div>
</template>
我還是不會用怎么辦
把上面的github下載下來,沒用的代碼刪了,改成自己的就行