vue服务端渲染之nuxtjs
前言
本篇主要針對(duì)nuxtjs中的一些重要概念整理和代碼實(shí)現(xiàn)!
在學(xué)習(xí)vue服務(wù)端渲染之前,先搞清楚幾個(gè)概念:
- 什么是客戶端渲染(CSR)
- 什么是服務(wù)端渲染(SSR)
- CSR和SSR有什么異同
客戶端渲染(CSR):當(dāng)用戶在瀏覽器中輸入網(wǎng)址,打開網(wǎng)頁,此時(shí)的頁面只有樣式和一些html代碼構(gòu)成的空殼頁面,并沒有數(shù)據(jù)。這就需要我們通過執(zhí)行js代碼,請(qǐng)求相關(guān)數(shù)據(jù),請(qǐng)求到數(shù)據(jù)之后,通過模板(vue),將這些數(shù)據(jù)渲染到頁面,最終呈現(xiàn)給用戶完整的頁面。
服務(wù)端渲染(SSR):當(dāng)用戶在瀏覽器中輸入網(wǎng)址,打開網(wǎng)頁,此時(shí)的后端會(huì)根據(jù)請(qǐng)求的網(wǎng)址,拿到相關(guān)頁面并將數(shù)據(jù)填入到頁面,完成頁面的渲染,最后將完整的頁面返回給客戶端(瀏覽器),呈現(xiàn)到用戶面前。
利弊:客戶端渲染,就是現(xiàn)在非常流行的前后分離的開發(fā)模式,極大程度的減輕后端壓力,但是對(duì)SEO不友好;服務(wù)端渲染對(duì)SEO友好,但是對(duì)后端以及服務(wù)器的性能要求較高。
正文:
先用node實(shí)現(xiàn)簡易的服務(wù)端渲染:
const Vue = require('vue'); const server = require('express')();server.get('/',(req,res)=>{//創(chuàng)建 Vue 實(shí)例const app = new Vue({template:`<div>hello vue SSR!</div>`})// 創(chuàng)建 renderer 沒下載的需要手動(dòng)下載 npm i vue-server-renderer --save改包用于vue的服務(wù)端渲染const renderer = require('vue-server-renderer').createRenderer(); renderer.renderToString(app).then(html=>{res.send(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body>${html}</body></html>`)}).catch(err=>{console.log(err)}) })server.listen(8001)檢查源代碼:
?
下面使用nuxjs來構(gòu)建項(xiàng)目!
首先,使用nuxt提供的腳手架(?create-nuxt-app)構(gòu)建項(xiàng)目,根據(jù)提示一步一步操作。
主要注意倆點(diǎn):1.Choose custom server framework,選擇服務(wù)端渲染的框架,我們這里選擇的是express
2.Choose rendering mode 這個(gè)選項(xiàng)可以選擇SSR和Single Page APP,因?yàn)槲覀冃枰獦?gòu)建服務(wù)端渲染多頁面項(xiàng)目,所以這里選擇SSR
下面就是項(xiàng)目創(chuàng)建完成后的目錄結(jié)構(gòu):
目錄結(jié)構(gòu):
- assets::資源目錄,用于組織未編譯的靜態(tài)資源(css文件、圖標(biāo)等)
- components:?組件目錄,用于組織應(yīng)用的vue組件,這些組件不會(huì)像頁面組件(pages中的組件)那樣有?asyncData?方法的特性
- layouts:布局目錄,用于組織應(yīng)用的布局組件,若無額外配置,該目錄不能被重命名
- middleware:中間件目錄,用于存放應(yīng)用的中間件
- pages:?頁面目錄,用于組織應(yīng)用的路由及視圖,Nuxt.js 框架讀取該目錄下所有的 .vue文件并自動(dòng)生成對(duì)應(yīng)的路由配置,若無額外配置,該目錄不能被重命名
- plugins:插件目錄,用于組織那些需要在?根vue.js應(yīng)用實(shí)例化之前需要運(yùn)行的JavaScript插件
- server:服務(wù)器目錄,用于服務(wù)端渲染
- static:靜態(tài)文件目錄,用于存放應(yīng)用的靜態(tài)文件,此類文件不會(huì)被 Nuxt.js 調(diào)用 Webpack 進(jìn)行構(gòu)建編譯處理。服務(wù)器啟動(dòng)的時(shí)候,該目錄下的文件會(huì)映射至應(yīng)用的根路徑 / 下(/static/robots.txt?映射至 /robots.txt),若無額外配置,該目錄不能被重命名
- store:store目錄,用于組織應(yīng)用的?Vuex狀態(tài)樹?文件,若無額外配置,該目錄不能被重命名
- nuxt.config.js:用于組織 Nuxt.js 應(yīng)用的個(gè)性化配置,以便覆蓋默認(rèn)配置,若無額外配置,該目錄不能被重命名
- package.json:用于描述應(yīng)用的依賴關(guān)系和對(duì)外暴露的腳本接口,該文件不能被重命名
?項(xiàng)目跑起來:
檢查源碼,頁面的所有數(shù)據(jù)都能夠看到。
接下來正式學(xué)習(xí)nuxt,主要分為3部分:
- 生命周期
- 路由
- vuex狀態(tài)樹
一、生命周期(鉤子函數(shù))
- nuxtServerInit => 服務(wù)器初始化
- middleware => 中間件
- validate() => 參數(shù)校驗(yàn)
- asyncData()和fetch() => 異步數(shù)據(jù)處理
- render => 客戶端渲染
- beforeCreate和created => 這倆個(gè)鉤子服務(wù)端和客戶端都存在,可以拿到服務(wù)端的上下文以及組件本身
下面具體使用這些生命周期:
1.nuxtServerInit?
在store目錄中新建index.js:
export const actions = {nuxtServerInit(store, context){console.log('我是nuxtServerInit生命周期!', store, context)} }項(xiàng)目跑起來后,在終端會(huì)打印相關(guān)信息:
?2.middleware?
middleware中間件可以在配置文件中、布局頁面、頁面組件中使用。
在nuxt.config.js配置文件中,將middleware寫進(jìn)router配置中,可以進(jìn)行全局的導(dǎo)航守衛(wèi):
module.exports = {mode: 'universal',router: {middleware: 'isLogin'},...接下來在middleware目錄中新建isLogin.js文件:
export default (context) => {console.log('我是全局守衛(wèi)') }打印:
?在布局頁面中使用中間件,在layout目錄下的default.vue中:
<script>export default {// middleware: 'isLogin',middleware(){console.log('我是布局頁面的中間件!')},} </script>在頁面組件內(nèi)中間件可以直接寫外部之前定義的中間件,也可以用中間件鉤子!
?同樣,在頁面組件中,pages目錄下的index.vue中:
<script>export default {// middleware: 'isLogin',middleware(){console.log('我是頁面組件的中間件!')},} </script>?通過打印的順序可以看出,中間件的執(zhí)行順序是:nuxt.config.js文件 => 布局文件 => 頁面文件,所有中間件函數(shù)的參數(shù)中,都能拿到服務(wù)端的上下文信息context。
3.validate()
validate()需要定義在頁面組件內(nèi),即pages目錄下的頁面,可以用來進(jìn)行參數(shù)校驗(yàn),攔截是否可以進(jìn)入該頁面。具體實(shí)現(xiàn),在pages/index.vue中:
//js 代碼 export default {// middleware: 'isLogin',middleware(){console.log('我是頁面組件的中間件!')},validate(context){const {params, query} = context;//對(duì)傳過來的params和query進(jìn)行校驗(yàn)//...console.log('我是頁面validate參數(shù)校驗(yàn)')return true; // 校驗(yàn)通過,校驗(yàn)失敗返回false,進(jìn)入到404頁面},components: {Logo} }4.asyncData()和fetch()
這倆個(gè)鉤子也是在頁面組件中使用,pages/index.vue中:
asyncData(context){// 異步讀取數(shù)據(jù),處理相關(guān)業(yè)務(wù)邏輯console.log('我是asyncData')//最后返回?cái)?shù)據(jù)return {a: 1};},fetch(context){// 異步讀取數(shù)據(jù),處理相關(guān)業(yè)務(wù)邏輯,將數(shù)據(jù)提交給vuexconsole.log('我是fetch')},5.beforeCreate()和created()
?在pages/index.vue中:
... fetch(context){// 異步讀取數(shù)據(jù),處理相關(guān)業(yè)務(wù)邏輯,將數(shù)據(jù)提交給vuexconsole.log('我是fetch')},beforeCreate() {console.log('我是beforeCreate')},created() {console.log('我是create')}, ...客戶端:
服務(wù)端:
二、路由(約定式、自定義)
1.約定式路由:依據(jù)pages目錄結(jié)構(gòu)自動(dòng)生成 vue-router模塊的路由配置。
1>基礎(chǔ)路由:
在layouts目錄中的default.vue:
<template><div><!-- 聲明式跳轉(zhuǎn):nuxt-link跟vue中的router-link一樣,負(fù)責(zé)路由跳轉(zhuǎn) --><nuxt-link to="/">首頁</nuxt-link><nuxt-link to="/tabA">tabA</nuxt-link><nuxt-link to="/tabB">tabB</nuxt-link><!-- 展示區(qū),跟vue中的router-view一樣 --><nuxt/></div> </template>效果:
?
?
nuxt自動(dòng)生成的路由為:
router: {routes: [{name: 'index',path: '/',component: 'pages/index.vue'},{name: 'tabA',path: '/tabA',component: 'pages/tabA.vue'},{name: 'tabB',path: '/tabB',component: 'pages/tabB.vue'}] }2>動(dòng)態(tài)路由
?在 Nuxt.js 里面定義帶參數(shù)的動(dòng)態(tài)路由,需要?jiǎng)?chuàng)建對(duì)應(yīng)的以下劃線“_”作為前綴的 Vue 文件 或 目錄。
?pages/tabA.vue:
<template><div><h3>tabA</h3><!-- 倆種路由傳參方式 --><nuxt-link to="/tabA/1?a=tabA1">tabA動(dòng)態(tài)路由1</nuxt-link><!-- name: 'tabA-id',這里-id必須是tabA目錄下的_id.vue的名稱,且將 _ 改為 - --><nuxt-link :to="{name: 'tabA-id', params:{id: 2}, query:{a: 'tabA2'}}">tabA動(dòng)態(tài)路由2</nuxt-link><!-- 子路由的展示區(qū) --><nuxt/></div> </template>pages/tabA/_id.vue:
<template><div><h3>我是tabA的動(dòng)態(tài)路由頁</h3></div> </template>效果:
這里會(huì)發(fā)現(xiàn)一個(gè)問題,路由為/tabA的時(shí)候,也打開了動(dòng)態(tài)路由頁,這是因?yàn)楫?dāng)路由為tabA時(shí),會(huì)發(fā)現(xiàn)它下面還有子路由,由于沒有指定默認(rèn)的頁面,因此會(huì)找到一個(gè)子路由進(jìn)行渲染。為了解決這個(gè)問題,可以在tabA目錄下新建一個(gè)index.vue。
tabA/index.vue:
<template><h3>我是動(dòng)態(tài)子路由默認(rèn)頁</h3> </template>?此時(shí)nuxt自動(dòng)生成的路由信息為:
router: {routes: [{name: 'index',path: '/',component: 'pages/index.vue'},{name: 'tabA-id',path: '/tabA/:id',component: 'pages/tabA/_id.vue'},... ] }?
3>嵌套路由
?創(chuàng)建內(nèi)嵌子路由,你需要添加一個(gè) Vue 文件,同時(shí)添加一個(gè)與該文件同名的目錄用來存放子視圖組件。
?在tabA目錄下新建tabC目錄和tabC.vue
?tabA/tabC.vue:
<template><div><!-- 子路由展示區(qū) --><nuxt></nuxt></div> </template>tabA/tabC/index.vue:
<template><div><h3>我是tabC</h3></div> </template>?從上圖可以看到倆種顏色的區(qū)域,是倆級(jí)展示區(qū),通常我們一個(gè)頁面只需要一個(gè)展示區(qū),因此需要改一下項(xiàng)目的目錄結(jié)構(gòu):
?在pages目錄下的頁面為一級(jí)展示區(qū),其中有tabA、tabB,接下來將tabA目錄下的頁面也設(shè)置成一級(jí)展示區(qū)。將tabA.vue內(nèi)容拷貝至tabA目錄下的index.vue中,并刪除tabA.vue。
2、擴(kuò)展路由
nuxt中支持聲明式路由,同樣也支持自定義路由,根據(jù)路由渲染指定的視圖,需要在nuxt.config.js中配置:
router: {extendRoutes(routes, resolve){routes.push({name: 'tabD',path: '/tabD',component: resolve(__dirname, 'pages/extendTabD.vue')})}},在pages中新建extendTabD.vue:
pages/extendTabD.vue:
<template><div><h3>我是擴(kuò)展路由tabD</h3></div> </template>在layouts/default.vue中增加鏈接:
<nuxt-link to="/tabD">tabD</nuxt-link>3、路由過渡動(dòng)畫
1>全局過渡動(dòng)畫:
transition.css:
/* 路由統(tǒng)一動(dòng)效 *//* 動(dòng)畫形式 */ .page-enter-active, .page-leave-active{transition: opacity .5s; }/* 入 退 */ .page-enter, .page-leave-active{opacity: 0; }nuxt.config.js:
/*** Global CSS*/css: ['assets/css/transition.css'],2>局部過渡動(dòng)畫
extendTabD.vue:
<template><div><h3>我是擴(kuò)展路由tabD</h3></div> </template><script>export default {name: "extendTabD",transition: 'tabd'} </script><style scoped>.tabd-enter-active, .tabd-leave-active{transition: .5s ease all;}.tabd-enter, .tabd-leave-active{margin-left: -1000px;} </style>?4、路由守衛(wèi)
- 前置
- 后置
- 組件內(nèi)部
1>前置:
全局守衛(wèi):
- 在nuxt.config.js的router內(nèi)引入middleware
- 在layouts文件中定義middleware
- 在插件中定義前置全局守衛(wèi),beforeEach()
組件獨(dú)享守衛(wèi):
- 在頁面中引入middleware或middleware()
- 跟vue-router一樣的組件內(nèi)部鉤子函數(shù),比如:beforeRouteEnter
2>后置:
在插件中定義后置全局守衛(wèi),afterEach()
?具體實(shí)現(xiàn):
全局守衛(wèi):
?a.借助middleware實(shí)現(xiàn)前置的路由守衛(wèi),在生命周期的middleware中已經(jīng)基本實(shí)現(xiàn)。
middleware/isLogin.js:
export default (context) => {console.log('我是全局守衛(wèi)')const {redirect} = contextredirect('/tabD') // 利用redirect()頁面重定向 }b.借助插件實(shí)現(xiàn)前置、后置全局守衛(wèi):
plugins/router.js
export default ({app, redirect})=>{//app == vue實(shí)例//全局前置守衛(wèi),進(jìn)入的頁面不是tabA的頁面,都會(huì)重定向到tabAapp.router.beforeEach((to, from, next)=>{if(to.name == 'tabA'){next()}else{redirect({name: 'tabA'})}});//全局后置守衛(wèi)app.router.afterEach((to, from)=>{console.log('我是后置守衛(wèi)')}) }nuxt.config.js中配置plugins:
plugins: ['~/plugins/router.js'],組件內(nèi)守衛(wèi):
extendTabD.vue:
<template><div><h3>我是擴(kuò)展路由tabD</h3></div> </template><script>export default {name: "extendTabD",transition: 'tabd',beforeRouteEnter(to, from, next){alert('haha');}} </script>?
三、vuex狀態(tài)樹
?nuxt中集成了vuex,因此我們?cè)陧?xiàng)目中不需要下載引入,而是按照約定使用:
?nuxt會(huì)嘗試找到 src 目錄(默認(rèn)是應(yīng)用根目錄)下的?store?目錄,如果該目錄存在,它將做以下的事情:
nuxt中的vuex使用方式跟vue中大同小異,除了不需要手動(dòng)引入之外,在模塊化中,store目錄下的每個(gè)子模塊(除了根模塊index.js),都會(huì)被轉(zhuǎn)換成為狀態(tài)樹指定命名的子模塊,其它使用方式跟vue項(xiàng)目中的vuex基本相同。
store/index.js:
const state = ()=>({}) const mutations = {}const actions = {} export default {state,mutations,actions }store/user.js:
const state = () => ({isLogin: false })const mutations = {change_Login(state, isLogin) {state.isLogin = isLogin} }const actions = {changeLogin({commit},isLogin) {commit('change_Login', isLogin)} } export default {namespaced: true,state,mutations,actions }pages/tabB.vue:
<template><div><h3>tabB</h3><h4>登錄狀態(tài):{{isLogin ? '已登錄' : '未登錄'}}</h4></div> </template><script>import { mapState } from 'vuex';export default {name: "TabB",computed:{...mapState({isLogin: state => state.user.isLogin})},created() {setTimeout(()=>{this.$store.dispatch('user/changeLogin', true)}, 1000)}} </script>效果:一秒后,登錄狀態(tài)從“未登錄”變?yōu)椤耙训卿洝薄?/p>
?
以上就是我對(duì)nuxt學(xué)習(xí)過程中一些重要概念的理解和實(shí)踐,有什么不足歡迎評(píng)論指正!
腳踏實(shí)地行,海闊天空飛
總結(jié)
以上是生活随笔為你收集整理的vue服务端渲染之nuxtjs的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DAO开发实战业务分析
- 下一篇: Vue 实时获取文本框内容