1. 程式人生 > >Nuxt.js 基礎入門教程

Nuxt.js 基礎入門教程

原文連結

Vue 開發一個單頁面應用,相信很多前端工程師都已經學會了,但是單頁面應用有一個致命的缺點,就是 SEO 極不友好。除非,vue 能在服務端渲染(ssr)並直接返回已經渲染好的頁面,而並非只是一個單純的 <div id="app"></div>

Nuxt.js 就是一個極簡的 vue 版的 ssr 框架。基於它,我們可以快速開發一個基於 vue 的 ssr 單頁面應用。

安裝

Nuxt.js 官方提供了一個模板,可以使用 vue-cli 直接安裝。

$ vue init nuxt-community/starter-template <project-name>

目錄結構

.
├── README.md
├── assets
├── components
├── layouts
├── middleware
├── node_modules
├── nuxt.config.js
├── package.json
├── pages
├── plugins
├── static
├── store
└── yarn.lock

其中:

  1. assets: 資原始檔。放置需要經過 webpack 打包處理的資原始檔,如 scss,圖片,字型等。
  2. components: 元件。這裡存放在頁面中,可以複用的元件。
  3. layouts: 佈局。頁面都需要有一個佈局,預設為 default。它規定了一個頁面如何佈局頁面。所有頁面都會載入在佈局頁面中的 <nuxt />
     標籤中。如果需要在普通頁面中使用下級路由,則需要在頁面中新增 <nuxt-child />該目錄名為Nuxt.js保留的,不可更改。
  4. middleware: 中介軟體。存放中介軟體。可以在頁面中呼叫: middleware: 'middlewareName' 。
  5. pages: 頁面。一個 vue 檔案即為一個頁面。index.vue 為根頁面。

    1. 若需要二級頁面,則新增資料夾即可。
    2. 如果頁面的名稱類似於 _id.vue (以 _ 開頭),則為動態路由頁面,_ 後為匹配的變數(params)。
    3. 若變數是必須的,則在資料夾下建立空檔案 index.vue。更多的配置請移步至 
      官網
       。
  6. plugin: 外掛。用於組織那些需要在 根vue.js應用 例項化之前需要執行的 Javascript 外掛。需要注意的是,在任何 Vue 元件的生命週期內, 只有 beforeCreate 和 created 這兩個鉤子方法會在 客戶端和服務端均被呼叫。其他鉤子方法僅在客戶端被呼叫。
  7. static: 靜態檔案。放置不需要經過 webpack 打包的靜態資源。如一些 js, css 庫。
  8. store: 狀態管理。具體使用請移步至 官網
  9. nuxt.config.jsnuxt.config.js 檔案用於組織Nuxt.js 應用的個性化配置,以便覆蓋預設配置。具體配置請移步至 官網

Nuxt 特有函式

首先,瞭解一下在 nuxt 的頁面中獨有的函式/變數:

asyncData(context)

asyncData方法使得你能夠在渲染元件之前非同步獲取資料。該方法在服務端中執行的,所以,請求資料時,不存在跨域問題。返回的資料將與 data() 返回的資料進行合併。由於asyncData方法是在元件 初始化 前被呼叫的,所以在方法內是沒有辦法通過 this 來引用元件的例項物件。

context 變數的可用屬性一覽:

屬性欄位 型別 可用 描述
isClient Boolean 客戶端 & 服務端 是否來自客戶端渲染
isServer Boolean 客戶端 & 服務端 是否來自服務端渲染
isDev Boolean 客戶端 & 服務端 是否是開發(dev) 模式,在生產環境的資料快取中用到
route 客戶端 & 服務端 vue-router 路由例項。
store 客戶端 & 服務端 Vuex.Store 例項。只有vuex 資料流存在相關配置時可用。
env Object 客戶端 & 服務端 nuxt.config.js 中配置的環境變數, 見 環境變數 api
params Object 客戶端 & 服務端 route.params 的別名
query Object 客戶端 & 服務端 route.query 的別名
req 服務端 Node.js API 的 Request 物件。如果 nuxt 以中介軟體形式使用的話,這個物件就根據你所使用的框架而定。nuxt generate 不可用
res 服務端 Node.js API 的 Response 物件。如果 nuxt 以中介軟體形式使用的話,這個物件就根據你所使用的框架而定。nuxt generate 不可用
redirect Function 客戶端 & 服務端 用這個方法重定向使用者請求到另一個路由。狀態碼在服務端被使用,預設 302。redirect([status,] path [, query])
error Function 客戶端 & 服務端 用這個方法展示錯誤頁:error(params)params 引數應該包含 statusCode和 message 欄位。

fetch(context)

fetch 方法用於在渲染頁面前填充應用的狀態樹(store)資料, 與 asyncData 方法類似,不同的是它不會設定元件的資料。為了讓獲取過程可以非同步,你需要返回一個 Promise,Nuxt.js 會等這個 promise 完成後再渲染元件。

fetch 會在元件每次載入前被呼叫(在服務端或切換至目標路由之前)。

head

Nuxt.js 使用了 vue-meta 更新應用的 頭部標籤(Head) 和 html 屬性

用於更新 頭部資訊。如 title,descripe 等。在 head 方法裡可通過 this 關鍵字來獲取元件的資料。

layout

指定該頁面使用哪個佈局檔案。預設值為 default

middleware

需要執行的中介軟體,如鑑權的 auth等。

transition

指定頁面切換時的動畫效果。支援傳入 StringObjectFunction。具體配置請移步至 官網 。

validate

Nuxt.js 可以讓你在動態路由對應的頁面元件中配置一個校驗方法用於校驗動態路由引數的有效性。

返回 true 說明路由有效,則進入路由頁面。返回不是 true 則顯示 404 頁面。

Begin Coding

前置工作

API

在這裡,我們使用 CNode API 進行開發 Demo.

axios

請求資料,我們使用 Nuxt 官方提供的 @nuxtjs/axios 安裝後,在 nuxt.config.js 中加上:

export default {
  ...
  modules: [
    '@nuxtjs/axios'
  ],
  axios: {
    baseURL: 'https://cnodejs.org/api/v1',
    // or other axios configs.
  }
  ...
}

就可以在頁面中通過 this.$axios.$get 來獲取資料,不需要在每個頁面都單獨引入 axios.

scss

需要先安裝 sass-loader 和 node-sass

$ yarn add sass-loader node-sass --dev

如果需要在專案中全域性使用某個 scss 檔案(如 mixins, vars 等),需要藉助 sass-resources-loader : yarn add sass-resources-loader —dev, 還需要在 nuxt.config.js 的 build 配置中調整匯出的 loader 配置:

export default {
  ...
  build: {
    extend(config, { isDev, isClient }) {
      const sassResourcesLoader = {  
        loader: 'sass-resources-loader',  
        options: {  
          resources: [
            // 填寫需要全域性注入 scss 的檔案。引入後,所有頁面均有效。
            'assets/styles/mixins.scss'  
          ]
        }  
      }
      // 修改 scss sass 引用的 loader。
      config.module.rules.forEach((rule) => {  
        if (rule.test.toString() === '/\\.vue$/') {  
          rule.options.loaders.sass.push(sassResourcesLoader)  
          rule.options.loaders.scss.push(sassResourcesLoader)  
        }  
        if (['/\\.sass$/', '/\\.scss$/'].indexOf(rule.test.toString()) !== -1) {  
          rule.use.push(sassResourcesLoader)  
        }  
      })  
    }
  }
  ...
}

首頁

首頁一般只需要簡單的獲取首頁資料並渲染即可。

主要 程式碼:

asyncData({app, query}) {
  console.log(query)
  // 根據不用的標籤獲取不同的資料,最後返回話題列表。
  return app.$axios.$get(`topics?tab=${query.tab || ''}`).then(res => {
    // console.log(res)
    // console.log(JSON.parse(res))
    return {list: res.data}
  })
}

當進入首頁時,該函式會被執行, nuxt 會等到獲取資料後再和元件的 data 合併,進而渲染資料。在模板中,可以直接使用 list 變數獲取資料。

<div class="card fluid topic" v-for="topic in list" :key="topic.id" >
  <div class="section">
    <h3><nuxt-link :to="{name: 'topic-id', params: {id: topic.id}}" class="topic-title">{{topic.title}}</nuxt-link></h3>
    <p class="topic-info">
      <mark v-if="topic.top" class="tertiary">精華</mark>
      <mark v-else>{{tabsObj[topic.tab]}}</mark>
      <span class="avatar">
        <img :src="topic.author.avatar_url" alt="">
      </span>
      <span class="username">
        {{topic.author.loginname}}
      </span>
    </p>
  </div>
</div>

在這裡提及一下, <nuxt-link /> 和 <a /> 的區別是: nuxt-link 走的是 vue-router 的路由,即網頁已為單頁面,並且瀏覽器不會重定向。而 a 標籤走的是 window.location.href,每一次點選 a 標籤後的頁面,都會進行一次服務端渲染,和普通的 PHP 混合開發沒有太大的區別。

在這裡使用了 nuxt-link 是因為 CNode 的 API 不存在跨域問題,因此可以作為一個單頁面應用,體驗更好。

因為列表頁資料型別有多種,該頁面可能會被複用,所以當路由物件發生變化時,需要重新獲取資料,這時可以監聽路由的變化以做出響應:

watch: {
  '$route': function() {
    console.log('$route has changed.')
    this.getData()
  }
}

配置 seo 優化(這裡只是單純的複製罷了,demo 使用,侵刪):

head() {
  return {
    title: '首頁' + (this.$route.query.tab ? `- ${this.tabsObj[this.$route.query.tab]}` : ''),
    meta: [{
      hid: 'description',
      name: 'description',
      content: 'CNode:Node.js專業中文社群'
    }]
  }
}

話題詳情

同樣的,使用 asyncData 函式進行獲取資料,再渲染頁面。

asyncData({app, params}) {
  console.log(params)
  return app.$axios.$get('topic/' + params.id).then(res => {
    // let data = res.data instanceof String ? JSON.parse(res.data) : res.data
    let data = res.data
    // console.log(res)
    // let div = document.createElement('div')
    // div.innerHTML = res.data.data.content
    // res.data.summary = div.innerText.substr(0, 120)
    data.summary = data.content.replace(/<[^>]+>/g,"").substr(0, 120).replace(/\s+/g, '')
    return {detail: data}
  }).catch(err => {
    console.log('axios.get failed.')
    console.error(err)
  })
}

在這裡,踩過坑。想使用 div 的 innerText 來過濾掉正文中的 HTML 標籤,但是,如果使用者是直接進入這個頁面的時候,執行 asyncData 時,document 物件是不存在的,從而會報錯。也就是說,當 asyncData 在服務端執行時,是沒有 document和 window 物件的,請大家注意一下。

作為一個社群,seo 尤為重要,倘若每個頁面都需要寫一大堆的 head 物件,就會顯得尤其的繁瑣。所以可以藉助 nuxt 的 plugin 機制,將其封裝成一個函式,並注入到每一個頁面當中:

// plugins/global.js
import Vue from 'vue'

Vue.mixin({
  methods: {
    // 必傳 標題,描述。其他的 meta 標籤通過 payload 注入,其中,每個 meta 的 hid 需要是唯一的。
    $seo(title, content, payload = []) {
      return {
        title,
        meta: [{
          hid: 'description',
          name: 'description',
          content
        }].concat(payload)
      }
    }
  }
})

在 nuxt.config.js 中加上:

export default {
  plugins: [
    '~plugins/global.js'
  ]
}

這樣,只需要在頁面的 head 的函式中,返回該函式即可:

head() {
    return this.$seo(this.detail.title, this.detail.summary)
}

詳情頁 seo

可見,詳情頁已經成功的設定了部分 seo 的標籤。

以上是 Nuxt 的一些基礎配置及應用。

我再去研究一下, fetch 和 store 的結合,將該 demo 繼續完善。