詳解Nuxt.js 實戰(zhàn)集錦
讀本文前,請先熟讀nuxt
官方文檔,并且具備一定的vue.js
相關(guān)開發(fā)經(jīng)驗
一、CSR和SSR對比
在SPA
之前的時代,我們傳統(tǒng)的Web
架構(gòu)大都是SSR
,如:Wordpress(PHP)
、JSP
技術(shù)、JavaWeb
等這些程序都是傳統(tǒng)典型的SSR
架構(gòu),即:服務(wù)端取出數(shù)據(jù)和模板組合生成 html
輸出給前端,前端發(fā)生請求時,重新向服務(wù)端請求html
資源。
SPA(CSR):
SPA
應(yīng)用,到了Vue
、React
,單頁面應(yīng)用優(yōu)秀的用戶體驗,逐漸成為了主流,頁面整體是javaScript
渲染出來的,稱之為客戶端渲染CSR
。SPA
渲染過程。由客戶端訪問URL
發(fā)送請求到服務(wù)端,返回HTML
結(jié)構(gòu)(但是SPA
的返回的HTML
結(jié)構(gòu)是非常的小的,只有一個基本的結(jié)構(gòu))。客戶端接收到返回結(jié)果之后,在客戶端開始渲染HTML
,渲染時執(zhí)行對應(yīng)javaScript
,最后渲染template
,渲染完成之后,再次向服務(wù)端發(fā)送數(shù)據(jù)請求,注意這里時數(shù)據(jù)請求,服務(wù)端返回json
格式數(shù)據(jù)。客戶端接收數(shù)據(jù),然后完成最終渲染。
CSR原理圖
CSR多數(shù)是基于webpack構(gòu)建的項目,編譯出來的html文件,資源文件都被打包到j(luò)s中,這樣的頁面是不利于搜索引擎優(yōu)化(SEO, Search Engine Optimization)
,并且內(nèi)容到達時間(time-to-content) (或稱之為首屏渲染時長)
也有很大的優(yōu)化空間
簡單來講,SPA
雖然給服務(wù)器減輕了壓力,也存在比較明顯的兩個缺點:
- 首屏渲染時間比較長:必須等待
JavaScript
加載完畢,并且執(zhí)行完畢,才能渲染出首屏。 SEO
不友好:爬蟲只能拿到一個div
元素,認為頁面是空的,不利于SEO
。
什么是SEO
呢?SEO
即通過各種技術(shù)(手段)來確保,你的Web
內(nèi)容被搜素引擎最大化收錄,最大化提高權(quán)重,帶來更多流量。大部分的搜索引擎僅能抓取URI
直接輸出的數(shù)據(jù)資源,對于 Ajax
類的異步請求的數(shù)據(jù)無法抓取
因此,對于那些展示宣傳型頁面,如官網(wǎng),必須進行服務(wù)端渲染
SSR:
為了解決如上兩個問題,出現(xiàn)了SSR
解決方案,后端渲染出首屏的DOM
結(jié)構(gòu)返回,前端拿到內(nèi)容帶上首屏,后續(xù)的頁面操作,再用單頁面路由和渲染,稱之為服務(wù)端渲染(SSR)
。
SSR
渲染流程是這樣的,客戶端發(fā)送URL
請求到服務(wù)端,在服務(wù)端做出html
和數(shù)據(jù)
的渲染,渲染完成之后返回html
結(jié)構(gòu),客戶端拿到頁面的html
結(jié)構(gòu)渲染首屏。所以用戶在瀏覽首屏的時候速度會很快,因為客戶端不需要再次發(fā)送ajax
請求。并不是做了SSR
我們的頁面就不屬于SPA
應(yīng)用了,它仍然是一個獨立的spa
應(yīng)用。
SSR原理圖
SSR
是處于CSR
與SPA
應(yīng)用之間的一個折中的方案,在渲染首屏的時候在服務(wù)端做出了渲染,注意僅僅是首屏,其他頁面還是需要在客戶端渲染的,在服務(wù)端
接收到請求之后并且渲染出首屏頁面,會攜帶著剩余的路由信息預(yù)留給客戶端
去渲染其他路由的頁面。
vueSSR
將本來要放在瀏覽器執(zhí)行創(chuàng)建的組件,放到服務(wù)端先創(chuàng)建好,然后生成對應(yīng)的html將它們直接發(fā)送到瀏覽器,最后將這些靜態(tài)標記"激活"為客戶端上完全可交互的應(yīng)用程序。
在瀏覽器第一次訪問某個URI
資源的時候(首屏),Web
服務(wù)器根據(jù)路由拿到對應(yīng)數(shù)據(jù)渲染并輸出,且輸出的數(shù)據(jù)中包含兩部分:
- 路由頁對應(yīng)的頁面及已渲染好的數(shù)據(jù)
- 完整的SPA程序代碼
在首屏渲染完成之后,此時我們看到的其實已經(jīng)是一個和之前的SPA
相差無幾的應(yīng)用程序了,接下來我們進行的任何操作都只是客戶端的應(yīng)用進行交互,頁面/組件由Web
端渲染,路由也由瀏覽器控制,用戶只需要和當前瀏覽器內(nèi)的應(yīng)用打交道就可以了。
vueSSR原理圖
webpack
將 Source
打包出兩個bundle
文件:其中 Server Bundle
用于服務(wù)端渲染,服務(wù)端通過渲染器 bundleRenderer
將 bundle
生成首屏html
片段;而 Client Bundle
用于客戶端渲染,首屏外的交互和數(shù)據(jù)處理還是需要瀏覽器執(zhí)行 Client Bundle
來完成
缺點:
- 開發(fā)條件所限。瀏覽器特定的代碼,只能在某些生命周期鉤子函數(shù)
(lifecycle hook)
中使用;一些外部擴展庫(external library)
可能需要特殊處理,才能在服務(wù)器渲染應(yīng)用程序中運行。 - 更多的服務(wù)器端負載。在
Node.js
中渲染完整的應(yīng)用程序,顯然會比僅僅提供靜態(tài)文件的server
更加大量占用CPU
資源,因此如果你預(yù)料在高流量環(huán)境(high traffic)
下使用,請準備相應(yīng)的服務(wù)器負載,并明智地采用緩存策略。
二、nuxt.js介紹
1. nuxt.js是什么?
Nuxt.js
是vue
官方推薦的一個基于Vue.js
的做Vue SSR
的通用應(yīng)用框架(開箱即用),集成了Vue,Vue-Router,Vuex,Vue-Meta
等組件/框架,內(nèi)置了webpack
用于自動化構(gòu)建,使我們可以更快速地搭建一個具有服務(wù)端渲染能力的應(yīng)用。
2. nuxt.js的優(yōu)勢?
作為框架,Nuxt.js
為 客戶端/服務(wù)端 這種典型的應(yīng)用架構(gòu)模式提供了許多有用的特性,例如異步數(shù)據(jù)加載、中間件支持、布局支持等。Nuxt.js
有以下比較明顯的特性
- 支持各種樣式預(yù)編譯器
SASS,LESS
等等 - 本地開發(fā)支持熱加載
HTML
頭部標簽管理(依賴vue-meta
實現(xiàn))- 自動代碼分層
- 強大的路由功能,支持異步數(shù)據(jù)(路由無需額外配置)
- 內(nèi)置
webpack
配置,無需額外配置
3. nuxt.js的使用
npm create nuxt-app <project-name>
4. nuxt.js目錄結(jié)構(gòu)
(layouts、pages、static、store、nuxt.config.js、package.json)是Nuxt保留的,不可以更改
5. nuxt.js渲染流程
- Incoming Request指的是瀏覽器發(fā)出一個請求,服務(wù)端接收請求后
- 要檢查當前有沒有nuxtServerInit這個配置項,如果有的話就先執(zhí)行這個函數(shù)。具體的作用和使用可參考官方文檔nuxtserverinit-方法
- middleware中間件,中間件允許您定義一個自定義函數(shù)運行在一個頁面或一組頁面渲染之前。也就是可以在 匹配布局(
layout
組件)前執(zhí)行某種操作,也可以在解析完layout
之后,解析page
組件前 執(zhí)行某種操作。可以理解為是路由器的攔截器的作用 - 驗證:validate(),可以配合高級動態(tài)路由去做驗證。如果校驗不通過,
Nuxt.js
將自動加載顯示404
錯誤頁面或500
錯誤頁面,或者進行重定向。 - 獲取數(shù)據(jù),asyncData方法獲取數(shù)據(jù)并返回給當前組件,fetch方法修改
vuex
的store
render
:最后進行渲染。將渲染后的頁面返回給瀏覽器,用戶在頁面進行操作,如果再次請求新的頁面,此時只會回到生命周期中的 middlerware 中,而非 nuxtServerInit。nuxt-link
,如果是發(fā)起一個新的路由,那么這個時候要從頭開始循環(huán)
我們把服務(wù)器端創(chuàng)建的 .vue
文件全部理解成組件,在服務(wù)器端環(huán)境(node)
通過 beforeCreate
和 created
這倆個生命周期節(jié)點后服務(wù)器端 vue
組件生命周期結(jié)束。返回頁面給瀏覽器,在客戶端環(huán)境(v8)
中這個 vue
組件實例創(chuàng)建后會在客戶端再次擁有生命周期,此時生命周期中有 mounted
等鉤子函數(shù)。
需要特別注意的是 nuxt
中沒有 mounted
鉤子函數(shù)也沒有組件實例,只有 beforeCreate/created
鉤子與 context
對象。beforeCreated()
和created()
這兩個生命周期函數(shù)是同時運行在服務(wù)端&&客戶端,vue的其他鉤子則運行在客戶端,所以beforeCreated()
和created()
不存在window
對象
三、nuxt.js渲染過程部分詳解
1、nuxtServerInit
舉例:打開網(wǎng)頁要立即顯示的內(nèi)容
// SSR方式: // 1、nuxtServerInit 方法 actions: { async nuxtServerInit({commit},{req,app}) { let {data: {province, city}} = await axios.get('/aa/bb') commit('home/setPosition',{province: '', city: ''}) } } // 2、middleware 屬性 middleware: async (ctx) => { let {data: {province, city}} = await axios.get('/aa/bb') } // NO-SSR vue 組件 mounted 函數(shù)發(fā)送請求
2、異步數(shù)據(jù) asyncData
asyncData
方法會在組件(限于頁面組件)每次加載渲染之前,即在服務(wù)端或路由更新之前被調(diào)用。在asyncData()
中可以處理請求得來的數(shù)據(jù),通過return
將處理后的數(shù)據(jù)返回給當前vue
組件的data
。再次強調(diào)這里不能使用this
,因為沒有組件實例,asyncData()
默認的參數(shù)是ctx
即content
對象。
該方法用來獲取數(shù)據(jù),在服務(wù)器端把異步獲取到的數(shù)據(jù)扔給瀏覽器,那是如何拋給瀏覽器的呢?
通過下發(fā)一個`script`標簽,然后在`window`上掛了一個對象這個對象,第一個是告訴你用的是哪個模板,第二個給你的是數(shù)據(jù)
3、布局
Nuxt.js布局方式如下圖所示:
nuxt.js
實現(xiàn)了一個新的概念,layout
布局,我們可以通過layout
布 局方便的實現(xiàn)頁面的多個布局之間方便的切換。具體開發(fā)的頁面中,如果使用默認布局,則不需指定頁面的布局,nuxt
框架會自動對沒有指定布局的頁面和default
布局進行關(guān)聯(lián)。如果需要指定布局,則在layout
字段中對布局進行指定。
<script> export default { layout: 'plusBuy', ... } </script> // 如果layout文件中建立了一個單獨的文件,則在使用中也要指定 <script> export default { layout: 'plusBuy/plusBuy', ... } </script>
四、nuxt爬坑
1、localhost
訪問可以,換成真實的ip
地址后訪問不了
解決方案:
- 確認有沒有開代理
- 在
package.json
里做如下配置
"config": { "nuxt": { "host": "0.0.0.0", "port": 3000 } }
2、接口跨域問題
解決方案
- 安裝
@nuxtjs/axios
、@nuxtjs/proxy
- 在
nuxt.config.js
做如下配置
modules: ['@nuxtjs/axios'], // 不需要加入@nuxtjs/proxy axios: { proxy: true }, proxy: { '/wlfrontend': { // 請求到 /wlfrontend 代理到請求 http://10.102.140.38:7001/wlfrontend target: 'http://10.102.140.38:7001', changeOrigin: true // 如果接口跨域,需要進行這個參數(shù)配置 }, '/scenery': { // 將'localhost:8080/scenery/xxx'代理到'https://m.ly.com/scenery/xxx' target: 'https://m.ly.com', // 代理地址 changeOrigin: true, // 如果接口跨域,需要進行這個參數(shù)配置 secure: false // 默認情況下,不接受運行在 HTTPS 上,且使用了無效證書的后端服務(wù)器。如果你想要接受,只要設(shè)置 secure: false } }
3、asyncDate fetch created
因為服務(wù)端客戶端都會走,如果不想在客戶端執(zhí)行?
async asyncData ({ query, store, req }) { if (!process.server) return } async fetch({ store, params }){ if (!process.server) return } created(){ if (!process.server) return },
4、頁面做緩存,也就是返回上一級保持數(shù)據(jù)不重新請求
解決方案:
在布局頁面處理,layout/default.vue
或者是自己建立的布局頁面
<template> <div class="plusBuy"> <nuxt keep-alive /> </div> </template>
5、nuxt
是把所有頁面的js都引入到主頁了?
在生產(chǎn)模式下,Nuxt.js
使用瀏覽器的預(yù)加載策略來預(yù)加載目標頁面的腳本資源。所以當用戶點擊某個鏈接時,會有一種秒開的感覺。預(yù)加載策略使得Nuxt.js
既可以保持代碼分離又能保證頁面訪問體驗。
<nuxt-link>
則是幫我們擴展了自動預(yù)獲取代碼分割頁面。可以使用no-prefetch
屬性 禁用
如果想要禁用,在nuxt.config.js
做如下配置
router: { prefetchLinks: false, // 全局禁用所有鏈接上的預(yù)取 } render: { resourceHints: false, // 添加prefetch和preload,以加快初始化頁面加載時間。如果有許多頁面和路由,可禁用此項 },
6、切換子路由的head
中外部引入腳本載入有延遲,所以在調(diào)用時報錯
注意:
1、引入腳本不要加async:true
,這樣的話腳本不會阻塞,在下面代碼有用到該腳本中的方式時,腳本可能還沒有加載完
2、需要每個小項目自己做個定制化頁面layout
,layout/我的目錄/我的頁面.vue 然后在定制化頁面中使用head()
加入腳本
export default { // 方式一: head: { script: [ { type: 'text/javascript', src: 'https://js.40017.cn/cn/min/??/touch/hb/c/bridge.2.1.4.js?v=2016053', defer: true } ] } // 方式二: head () { return { script: [ { type: 'text/javascript', src: 'https://js.40017.cn/cn/min/??/touch/hb/c/bridge.2.1.4.js?v=2016053', defer: true } ] } } }
7、滾動事件
如果html
和body
設(shè)置了100%,那么子頁面足夠長時滾動的話,滾動事件要綁定在子頁面上,因為body
的高度不是整個頁面的高度
// 1. 在子頁面父元素加 <template> <div class="plus" ref="mainPage"></div> </template> // 2. 樣式設(shè)置100%滾動 .plus { height: 100%; overflow-y: scroll; -webkit-overflow-scrolling : touch; } // 3. 再添加滾動事件 function scrollEvent() { var that = this; let dom = this.$refs.mainPage; dom.onscroll = function() { let wh = dom.scrollTop; // 頁面上滑,出現(xiàn) wh > 100 ? (that.showBackTop = true) : (that.showBackTop = false); // 未開通,頁面滑動至不出現(xiàn)頂部的立即開通按鈕時,底部的立即開通固定展示 if(that.memberRightsInfo && !that.memberRightsInfo.IsPlusMember){ if(document.querySelector('.tab') && document.querySelector('.tab').offsetTop){ let distance = document.querySelector('.tab').offsetTop; wh > distance - 50 ? (that.isShowFixedBtn = true) : (that.isShowFixedBtn = false); } } }; }
8、文件下建立了其他文件,比如store/plusBuy/index.js
,并沒有在store
下直接建立index.js
,如何使用?
原理:Nuxt把store中的index.js文件中所有的state、mutations、actions、getters都作為其公共屬性掛載到了store實例上,然而其他的文件則是使用的是命名空間,其對應(yīng)的命名空間的名字就是其文件名。
computed: { ...mapState('plusBuy', { nickName: state => state.nickName }) } ...mapMutations('plusBuy', { setCityId: 'setCityId' // 將 `this.setCityId()` 映射為 `this.$store.commit('setCityId')` }) ...mapActions('plusBuy', { login: 'login' // 將 `this.login()` 映射為 `this.$store.dispatch('login')` })
9、asyncData
不可以調(diào)用this
,如果有好多個異步或數(shù)據(jù)進行處理,如何優(yōu)化asyncData()
// 可以使用類 class A { aatest(aa){ console.log(aa) } } // 調(diào)用方法 async asyncData ({ query, store, req }) { var test = new A(); test.aatest(123); }
10、如何獲取cookie
// 服務(wù)端獲取cookie b_getToken(req = {},c_name){ if (req.headers && req.headers.cookie) { var req_Cookies = req.headers.cookie.split("; ") let tokens = '' req_Cookies.forEach(v => { if (v.indexOf(c_name + "=")>=0) { tokens = v } }) return tokens.split('=')[1] } else { return '' } } // 客戶端獲取cookie getCookie: function(c_name) { if (document.cookie.length > 0) { //先查詢cookie是否為空,為空就return "" let c_start = document.cookie.indexOf(c_name + "=") || ''; //通過String對象的indexOf()來檢查這個cookie是否存在,不存在就為 -1 if (c_start != -1) { c_start = c_start + c_name.length + 1; //最后這個+1其實就是表示"="號啦,這樣就獲取到了cookie值的開始位置 let c_end = document.cookie.indexOf(";", c_start); // 為了得到值的結(jié)束位置。因為需要考慮是否是最后一項,所以通過";"號是否存在來判斷 if (c_end == -1) { c_end = document.cookie.length; } return unescape(document.cookie.substring(c_start, c_end)); } } return ""; }, // 調(diào)用 let token = ''; if(process.server){ token = serverUtilsFn.b_getToken(req,'17uCNRefId'); console.log('server:' + token) }else { token = utilsFn.getCookie('17uCNRefId'); console.log('client:' + token) }
11、axios
數(shù)據(jù)處理問題,重復問題
import axios from 'axios'; import requestCheck from './requestCheck'; // 確保使用 axios.create創(chuàng)建實例后再使用。否則多次刷新頁面請求服務(wù)器,服務(wù)端渲染會重復添加攔截器,導致數(shù)據(jù)處理錯誤 const myaxios = axios.create() // axios.defaults.baseURL = "http://localhost:3000/" myaxios.interceptors.request.use(config => { let req = {...config }; req.url = req.method.toLocaleLowerCase() == 'post' ? requestCheck(req.url, req.data) : requestCheck(req.url, req.params); return req; }, error => { return Promise.reject(error) }) myaxios.interceptors.response.use(response => { return response }, error => { return Promise.reject(error) }) export default myaxios;
12、跳轉(zhuǎn)路由傳遞參數(shù)并且取值
傳遞參數(shù) -- this.$router.push({name: ' 路由的name ', params: {key: value}})
參數(shù)取值 -- this.$route.params.key
注: 使用這種方式,參數(shù)不會拼接在路由后面,地址欄上看不到參數(shù)
注意: 由于動態(tài)路由也是傳遞params的,所以在 this.$router.push() 方法中 path不能和params一起使用,否則params將無效。需要用name來指定頁面。
13、設(shè)置頁面動畫效果
/* 全局過渡動效設(shè)置 - 淡出 (fade) 效果*/ .page-enter-active, .page-leave-active { transition: opacity .5s; } .page-enter, .page-leave-active { opacity: 0; } /* 局部過渡動效設(shè)置 - 淡出 (fade) 效果*/ .test-enter-active, .test-leave-active { transition: opacity .5s; } .test-enter, .test-leave-active { opacity: 0; } // 在要使用的組件頁面中 export default { transition: 'test', }
14、如何使用插件
// 1. 安裝插件 yarn add swiper -D // 2. 引入 <script> import Swiper from 'swiper' </script> // 3. 引入樣式 <style lang="less" scoped> @import "../../node_modules/swiper/css/swiper.css"; </style>
15、如何在組件中使用異步數(shù)據(jù)
如果組件不是和路由綁定的頁面組件,原則上是不可以使用異步數(shù)據(jù)的。因為 Nuxt.js 僅僅擴展增強了頁面組件的data
方法,使得其可以支持異步數(shù)據(jù)處理。
對于非頁面組件,有兩種方式可以實現(xiàn)數(shù)據(jù)的異步獲?。?/p>
- 在組件的
mounted
方法里面實現(xiàn)異步獲取數(shù)據(jù)的邏輯,之后設(shè)置組件的數(shù)據(jù),限制是:不支持服務(wù)端渲染。 - 在頁面組件的
asyncData
或fetch
方法中進行API調(diào)用,并將數(shù)據(jù)作為props
傳遞給子組件。服務(wù)器渲染工作正常。缺點:asyncData
或頁面提取可能不太可讀,因為它正在加載其他組件的數(shù)據(jù)。
總之,使用哪種方法取決于你的應(yīng)用是否需要支持子組件的服務(wù)端渲染。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
原生js實現(xiàn)移動開發(fā)輪播圖、相冊滑動特效
原生JS實現(xiàn)圖片自動輪播緩沖切換特效,很實用流暢的圖片輪播特效,在較現(xiàn)代的瀏覽器上展現(xiàn)的圓角效果,兼容差點的是直角效果,全部原生JS實現(xiàn),還是很不錯的值得大家學習并利用,推薦下載。2015-04-04使用javaScript動態(tài)加載Js文件和Css文件
這篇文章主要介紹了如何使用javaScript動態(tài)加載Js文件和Css文件2015-10-10js報$ is not a function 的問題的解決方法
在html中的程序,跑的好好的,換成jsp在項目中跑,就一直報$ is not a function錯,針對此問題,下面有個不錯的解決方法,大家可以嘗試操作下2014-01-01JSON格式的時間/Date(2367828670431)/格式轉(zhuǎn)為正常的年-月-日 格式的代碼
這篇文章主要介紹了JSON格式的時間/Date(2367828670431)/格式轉(zhuǎn)為正常的年-月-日 格式的代碼的相關(guān)資料,需要的朋友可以參考下2016-07-07javascript實現(xiàn)iframe框架延時加載的方法
這篇文章主要介紹了javascript實現(xiàn)iframe框架延時加載的方法,可基于setTimeout實現(xiàn)這一功能,是非常實用的技巧,需要的朋友可以參考下2014-10-10