回憶殺,Nuxt.js

這兩年,我主要從事部門周邊業(yè)務(wù),搭建了數(shù)個(gè)細(xì)碎的 web 應(yīng)用。最近由于一些人事變動(dòng),我又回到了最初的產(chǎn)品線上。時(shí)光飛逝,欣賞著自己的遺(la)產(chǎn)(ji)代碼,“青驄”歲月浮現(xiàn)眼前。今天就談?wù)勎业谝粋€(gè)web技術(shù)棧——nuxt.js,一款server/client同構(gòu)渲染的 vue 框架。

安裝

當(dāng)年向領(lǐng)導(dǎo)層推薦 nuxt 的時(shí)候,我列了很多理由;雖然有些比較扯蛋,但當(dāng)時(shí)最核心的考量是,nuxt 可以幫助我們零基礎(chǔ)上手 web 應(yīng)用。我這里先說說如何安裝。nuxt 多年來一直使用的是 npm script 腳手架:操作就這么幾步:

  1. 在命令行里敲下:yarn create nuxt-app <my-project>
  2. 按默認(rèn)選項(xiàng)安裝依賴
  3. 一句yarn dev,開工了!

在 vue-cli 滿天飛的今天,npm 腳手架自然算不上什么驚奇的事情了,但幾年前能做到零基礎(chǔ)啟動(dòng) vue 是很不容易的。我記得那時(shí)候市面上的資料還是基于 html 掛載 vue.js——把 vue 當(dāng)作一個(gè)類似于 Jquery 的 lib 使用。作為練習(xí)項(xiàng)目而言,以上足夠了;但是,放到一個(gè)企業(yè)級項(xiàng)目里,你要考慮其他問題,比如瀏覽器兼容、開發(fā)體驗(yàn)、代碼風(fēng)格……這就意味著你要配齊 vue 全家桶,各色 webpack、eslint、babel 配置,集成三方的 UI 庫,布置后端服務(wù)等等等等。在一個(gè)幾乎沒有任何現(xiàn)代開發(fā)經(jīng)驗(yàn)的項(xiàng)目組里,該怎么上手?從零開始學(xué)所有配置,或是在 github 的某個(gè)角落找到大體接近你需求的 demo,然后開始修改自己也不懂的各色文件?

也許,經(jīng)歷過那段時(shí)期的老員工依舊會對 nuxt 抱有微詞,畢竟坑是不可抗拒的。但在前端開發(fā)方面,nuxt(相比于單純的 vue 庫)還是給我們帶來了很多驚喜:它封裝了上述所有配置,限制了代碼風(fēng)格和目錄結(jié)構(gòu),提供了一個(gè)相對舒適的開發(fā)環(huán)境,我們因此得以在最短時(shí)間內(nèi)投入到了生產(chǎn)實(shí)踐中。

目錄結(jié)構(gòu)

作為一個(gè)通用框架,nuxt 給我們提供了什么?我最早是 1.4 版本開始的第一個(gè) nuxt 項(xiàng)目,到今天是 2.11 版本,nuxt 的目錄結(jié)構(gòu)幾乎沒有變化:

├── .nuxt
├── pages
├── components
├── layouts
├── assets
├── static
├── store
├── plugins
├── middleware
├── nuxt.config.js
  • .nuxt

    nuxt 內(nèi)置了 webpack,當(dāng)運(yùn)行nuxt build后,它會將相關(guān)目錄的文件打包壓縮到.nuxt目錄下,方便進(jìn)一步應(yīng)用部署。

  • pages

    頁面文件目錄——vue 文件夾。該目錄下的目錄結(jié)構(gòu)和 vue 文件名會在 build 時(shí)映射為vue-router配置。我們來看看約定的路由規(guī)則:

    ├── pages
    │   ├── index.vue
    │   ├── login.vue
    │   └── dashboard
    │     └── about.vue
    

    上述目錄結(jié)構(gòu)最終會被描述為://login,以及/dashboard/about三個(gè)路由。還可以設(shè)置動(dòng)態(tài)路由;如下所示,該路由將被映射為/job/:id,并可以通過this.$route.params.id獲取路由參數(shù)。

    ├── pages
    │   └── job
    │     └── _id.vue
    

    此外,各個(gè)頁面以及后續(xù)的引入模塊,會以懶加載的形式在路由跳轉(zhuǎn)后import;相比于傳統(tǒng)的 spa(單頁面應(yīng)用),ssr(服務(wù)端渲染)的 nuxt 單次加載的資源更少,這也成為了 nuxt 剛出來時(shí)的一個(gè)賣點(diǎn)——首頁渲染快。

  • components

    組件目錄,顧名思義,用于放置 vue 的功能組件。注意,該目錄下的 vue 文件,不具有 nuxt 增強(qiáng)的生命周期鉤子,即它不能使用 asyncData、fetch 這類鉤子;這種限制我倒比較支持,組件和數(shù)據(jù)請求應(yīng)當(dāng)盡可能的正交化。

  • layouts

    第三個(gè) vue 文件目錄,放置 page 布局,通俗來說就是不同頁面的通用模版。比如:我們自己寫了一個(gè)叫/layouts/trophy.vue的獎(jiǎng)杯布局,

    <!-- layouts/trophy.vue -->
    <template>
      <div id="app">
        <header/>
        <main>
          <nuxt />
        </main>
        </rooter>
      </div>
    </template>
    

    pages 目錄下的頁面通過 layout 字段指定該類型模版,之后 page 內(nèi)容會填充到<nuxt />標(biāo)簽里。

    <!-- pages/index.vue -->
    <script>
    export default {
      layout: "trophy"
    };
    </script>
    
  • asserts 和 static

    asserts 和 static 就是所謂的資源目錄,存放 css、less、圖片等文件。Nuxt 集成了 css-loader、file-loader、url-loader 等 webpack 加載器,當(dāng)你把這些文件放在 asserts 目錄之下時(shí),nuxt 內(nèi)置的 webpack 會針對特定文件選擇特定加載器,打包、壓縮、拷貝到.nuxt 相應(yīng)路徑之下;而相對較大的資源文件,則可以放在 static 之下,webpack 將直接拷貝到.nuxt 之下

  • store

    store 對應(yīng)的就是 vuex 狀態(tài)樹文件。nuxt 自帶 vue 全家桶,自然也包含了 vuex。store 里的 js 文件最終會被 nuxt 構(gòu)件為 vuex 相關(guān)功能配置。

  • plugins & middlewar

    插件和中間件,用于定制化拓展。比如使用 vuetify,element 這類 ui 庫,我們就可以將相關(guān)配置放置在 plugins 里。又比如,你想對所有請求加權(quán)限控制,可以將配置放在 middlewar 里;所有資源的請求將先經(jīng)過這里的中間件,并做相應(yīng)的錯(cuò)誤處理。

  • nuxt.config.js

    nuxt 框架本身的配置文件,可以定制框架功能。最常用的修改就是在這里調(diào)整默認(rèn)的 webpack 配置了,而且最近幾版新添的配置項(xiàng),主要方向也是在暴露內(nèi)置 webpack 的 api。

nuxt 目錄結(jié)構(gòu)的定型,稍早于 vue-cli 推薦結(jié)構(gòu)的普及;目錄結(jié)構(gòu)看似合理,但是與主流風(fēng)格稍有不同,還是略顯美中不足的。在項(xiàng)目開始前,最好先確定這類代碼規(guī)范;這樣,當(dāng)切換項(xiàng)目、新手入門,或是多團(tuán)隊(duì)合作時(shí),能盡最大可能減少“解碼成本”——理解項(xiàng)目邏輯所需的成本。我參與的項(xiàng)目由于歷史慣性,之后都沿用了 nuxt 風(fēng)格。

生命周期

上面提到過 nuxt 框架對 vue 生命周期的增強(qiáng)。我們刨去 vue 自帶的生命周期(簡化為 Render),看看增強(qiáng)部分。

Nuxt Lifecycle
  • nuxtServerInit

    nuxtServerInit 只在首次請求到來時(shí)(第一次導(dǎo)航到站點(diǎn),或是刷新頁面時(shí)),在 server 端調(diào)用,與 client 端無關(guān)。設(shè)計(jì)的目的就是在 server 端異步請求數(shù)據(jù),并初始化全局 vuex 狀態(tài)樹。

  • middleware

    第二步是請求通過各類中間件。上面提到過,我們會在 middlewar 目錄下放置各類中間件作為請求的過濾器。中間件包括 nuxt.config.js 配置的全局中間件,和 layout、page 里指定的組件級中間件兩種。

    // middleware/authenticated.js
    export default function ({ store, redirect }) {
      if (!store.state.authenticated) {
        return redirect('/login')
      }
    }
    
    <!-- /pages/index.vue -->
    <script>
    export default {
      middleware: 'authenticated'
    }
    </script>
    
  • validate

    validate 是 page 里的鉤子方法,作用于動(dòng)態(tài)路由對映的頁面組件中。目的是配置一個(gè)校驗(yàn)方法,動(dòng)態(tài)檢驗(yàn)路由參數(shù)的有效性。

    <!-- /pages/job/_id.vue -->
    <script>
    export default {
        validate({ params, query, store }) {
          return true // if the params are valid
          return false // will stop Nuxt.js to render the route and display the error page
      }
    }
    </script>
    
  • asyncData / fetch

    再之后是兩種異步數(shù)據(jù)請求的鉤子。asyncData用于異步取數(shù),并初始化data(增強(qiáng) vue 生命周期里的data)。fetch也是異步取數(shù),但不返回?cái)?shù)據(jù),一般用作初始化局部使用的 vuex 狀態(tài)數(shù)——可以與 nuxtServerInit 比較一下。

    <script>
    export default {
      asyncData() {
        return axios.get("/api/data");
      },
      async fetch ({ store, params }) {
        let { stars } = await axios.get('http://api/stars');
        store.commit('setStars', stars);
      },
    };
    </script>
    

    asyncData / fetch 觸發(fā)的時(shí)機(jī)大家需要注意一下,client 和 server 都有可能觸發(fā)。在頁面初次加載時(shí),由 server 觸發(fā)數(shù)據(jù)請求;后續(xù)若有路由變化,會是在 client 觸發(fā)。

nuxt 增強(qiáng)的生命鉤子主要就是上面幾種,分工很明確。不過由于 SSR 的特性,server 和 client 端都有可能觸發(fā)這些鉤子,所以 debug 的時(shí)候,還是很容易搞混的。我這里列一下最常見的鉤子觸發(fā)時(shí)機(jī),大家注意一下:

Hooks Server(1st page) Client(1st page) Client(next pages)
nuxtServerInit ? ? ?
middleware ? ? ?
beforeCreate ? ? ?
asyncData/fetch ? ? ?
Created ? ? ?
mounted ? ? ?

小結(jié)

這期介紹了一款基于 vue 的 SSR 框架——nuxt。nuxt 框架對比 vue 庫的好處是:它提供了一套完整的解決方案,幫助團(tuán)隊(duì)統(tǒng)一命令、規(guī)范代碼、集成配套工具、封裝配置文件,以及提供生產(chǎn)和開發(fā)環(huán)境。這是對缺少文檔、缺乏經(jīng)驗(yàn),或是流動(dòng)性很大的團(tuán)隊(duì)來說,不可或缺的優(yōu)異功能。

不過,框架之于庫的劣勢也很鮮明:缺乏靈活性,缺少配套插件,封裝可能過深,項(xiàng)目侵入較大等等。我自己碰過最大的坑是,nuxt 對 serverless lambda 支持不友好;server 端的依賴不能打包部署,只能把所有 node_modules 一股腦扔到 lambda 上,結(jié)果超出了 lambda 上傳 size 限制。所幸,有個(gè)小姑娘花了兩禮拜時(shí)間,用 webpack 過濾掉了無用文件,從而大幅減少了 size。當(dāng)然,依賴問題可能是各種技術(shù)棧都會碰到的難題。我就見過有些部門專門組織多人團(tuán)隊(duì),花了一年時(shí)間才勉強(qiáng)解決依賴過大的問題,最后得到了領(lǐng)導(dǎo)層的高度贊揚(yáng)。

總之,框架和庫的選擇目前來說還是極度依個(gè)人經(jīng)驗(yàn),很難找出一套可以量化優(yōu)劣的評判機(jī)制;多寫代碼,積累經(jīng)驗(yàn),并在適當(dāng)時(shí)機(jī)轉(zhuǎn)型,可能是比較靠譜的應(yīng)對之道。(我也經(jīng)常在 nuxt 和 vue spa 之間搖擺,有時(shí)候還會被領(lǐng)導(dǎo)層噴“拍腦袋”,)

相關(guān)

文章同步發(fā)布于an-Onion 的 Github。碼字不易,歡迎點(diǎn)贊

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。