Nuxt.js 基礎入門教程

原文連接css

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

Nuxt.js 就是一個極簡的 vue 版的 ssr 框架。基於它,咱們能夠快速開發一個基於 vue 的 ssr 單頁面應用。前端

安裝

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

$ vue init nuxt-community/starter-template &lt;project-name&gt;node

目錄結構

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

其中:webpack

  1. assets: 資源文件。放置須要通過 webpack 打包處理的資源文件,如 scss,圖片,字體等。
  2. components: 組件。這裏存放在頁面中,能夠複用的組件。
  3. layouts: 佈局。頁面都須要有一個佈局,默認爲 default。它規定了一個頁面如何佈局頁面。全部頁面都會加載在佈局頁面中的 <nuxt /> 標籤中。若是須要在普通頁面中使用下級路由,則須要在頁面中添加 <nuxt-child />該目錄名爲Nuxt.js保留的,不可更改。
  4. middleware: 中間件。存放中間件。能夠在頁面中調用: middleware: 'middlewareName'
  5. pages: 頁面。一個 vue 文件即爲一個頁面。index.vue 爲根頁面。ios

    1. 若須要二級頁面,則添加文件夾便可。
    2. 若是頁面的名稱相似於 _id.vue (以 _ 開頭),則爲動態路由頁面,_ 後爲匹配的變量(params)。
    3. 若變量是必須的,則在文件夾下創建空文件 index.vue。更多的配置請移步至 官網
  6. plugin: 插件。用於組織那些須要在 根vue.js應用 實例化以前須要運行的 Javascript 插件。須要注意的是,在任何 Vue 組件的生命週期內, 只有 beforeCreatecreated 這兩個鉤子方法會在 客戶端和服務端均被調用。其餘鉤子方法僅在客戶端被調用。
  7. static: 靜態文件。放置不須要通過 webpack 打包的靜態資源。如一些 js, css 庫。
  8. store: 狀態管理。具體使用請移步至 官網
  9. nuxt.config.js: nuxt.config.js 文件用於組織Nuxt.js 應用的個性化配置,以便覆蓋默認配置。具體配置請移步至 官網

Nuxt 特有函數

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

asyncData(context)

asyncData方法使得你可以在渲染組件以前異步獲取數據。該方法在服務端中執行的,因此,請求數據時,不存在跨域問題。返回的數據將與 data() 返回的數據進行合併。因爲asyncData方法是在組件 初始化 前被調用的,因此在方法內是沒有辦法經過 this 來引用組件的實例對象。github

context 變量的可用屬性一覽:web

屬性字段 類型 可用 描述
isClient Boolean 客戶端 & 服務端 是否來自客戶端渲染
isServer Boolean 客戶端 & 服務端 是否來自服務端渲染
isDev Boolean 客戶端 & 服務端 是不是開發(dev) 模式,在生產環境的數據緩存中用到
route vue-router 路由 客戶端 & 服務端 vue-router 路由實例。
store vuex 數據流 客戶端 & 服務端 Vuex.Store 實例。只有vuex 數據流存在相關配置時可用。
env Object 客戶端 & 服務端 nuxt.config.js 中配置的環境變量, 見 環境變量 api
params Object 客戶端 & 服務端 route.params 的別名
query Object 客戶端 & 服務端 route.query 的別名
req http.Request 服務端 Node.js API 的 Request 對象。若是 nuxt 以中間件形式使用的話,這個對象就根據你所使用的框架而定。nuxt generate 不可用
res http.Response 服務端 Node.js API 的 Response 對象。若是 nuxt 以中間件形式使用的話,這個對象就根據你所使用的框架而定。nuxt generate 不可用
redirect Function 客戶端 & 服務端 用這個方法重定向用戶請求到另外一個路由。狀態碼在服務端被使用,默認 302。redirect([status,] path [, query])
error Function 客戶端 & 服務端 用這個方法展現錯誤頁:error(params)params 參數應該包含 statusCodemessage 字段。

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

指定頁面切換時的動畫效果。支持傳入 String, Object, Function。具體配置請移步至 官網

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) =&gt; {  
        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 =&gt; {
    // console.log(res)
    // console.log(JSON.parse(res))
    return {list: res.data}
  })
}

當進入首頁時,該函數會被執行, nuxt 會等到獲取數據後再和組件的 data 合併,進而渲染數據。在模板中,能夠直接使用 list 變量獲取數據。

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

在這裏說起一下, <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 =&gt; {
    // 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(/&lt;[^&gt;]+&gt;/g,"").substr(0, 120).replace(/\s+/g, '')
    return {detail: data}
  }).catch(err =&gt; {
    console.log('axios.get failed.')
    console.error(err)
  })
}

在這裏,踩過坑。想使用 div 的 innerText 來過濾掉正文中的 HTML 標籤,可是,若是用戶是直接進入這個頁面的時候,執行 asyncData 時,document 對象是不存在的,從而會報錯。也就是說,asyncData 在服務端執行時,是沒有 documentwindow 對象的,請你們注意一下。

做爲一個社區,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 的標籤。

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

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

Demo 線上地址
GitHub 地址

相關文章
相關標籤/搜索