Vue同構賦能之 NUXT 篇

上次和你們分享了VUE同構賦能 VUE SSR 篇,連接在這裏
juejin.im/post/5d5b4f…javascript

今天是下篇:NUXT
上篇有可能講的太複雜了😂,此次咋們直奔主題
仍是上次的 DEMO,咱們用NUXT來實現這個頁面
github地址在文章末尾 html

Nuxt

Nuxt是基於Vue ssr之上,集成了Vue-Router,Vuex,webpack等框架、組件的一個服務端渲染框架
在我看來,Nuxt就是一個升級版的Vue ssr,爲咱們預設了服務端渲染的應用所須要的各類配置
可是相應的,Nuxt的入侵性是特別高的,咱們須要理解Nuxt的思路,才能發揮它的優點。前端

目錄結構

首先咱們來看下Nuxt項目的目錄結構
vue

感受文件夾仍是不少的,可是仔細看看,就會發現很清晰了
沒有了先後端入口的配置,沒了webpack配置
咱們須要作的僅僅是"專一業務"

具體文件夾能夠看到java

assets - 前端資源文件
components - 前端通用組件
layouts - 前端佈局組件
store - vuex
static - 靜態文件webpack

plugins - 插件
middleware - 中間件ios

pages - 前端VUE頁面git

server - 後端github

其實這麼多文件,能夠分爲三個:前端,後端,資源web

約定大於配置

Nuxt中,處處體現着"約定大於配置"的思想,這一點咱們要注意
好比最簡單的,上面目錄文件夾的名字,若是沒有額外配置,是不能更更名字的
還有例如:layouts佈局文件中
default.vue是默認佈局頁
error.vue是默認錯誤頁面

好比pages前端頁面文件夾中,約定了自動生成router的規則
還有store文件夾中,默認index.js是默認狀態樹,
todos.js 文件則會自動生成todos 模塊
等等……

這些具體的下面會具體講到

佈局/路由

如今開始編寫咱們的demo, 先從layout佈局頁開始,咱們須要寫兩個頁面default.vue和error.vue
佈局頁就能夠看做咱們平時 vue 的 app.vue 入口頁面
可是他們有不一樣之處,Nuxt的子頁面內,能夠指定佈局頁(未指定就是default.vue)

default.vue

<template>
  <div>
    <header>
      <div class="head-content">
        <section class="head-left">
          一個幫助開發者成長的社區 |
          <nuxt-link to="/"> 首頁</nuxt-link>
        </section>
        <section class="head-right">
          <nuxt-link to="/user/login"> 登陸</nuxt-link>
          |
          <nuxt-link to="/user/register"> 註冊</nuxt-link>
        </section>
      </div>
    </header>
    <nuxt />
  </div>
</template>
複製代碼

子頁面設置layout

export default {
    // 默認就是default
    layout: "default",
}
複製代碼

<nuxt>與<nuxt-link>

咱們在佈局頁內能夠看到<nuxt>與<nuxt-link> 這兩個組件,有點似曾相識的感受
其實就是對應的<router-view><router-link>,
因此佈局頁或者咱們的嵌套路由的頁面,都須要用到<nuxt>組件
咱們能夠看到源碼內:

// nuxt
let routerView = [
  h('router-view', data)
]
if (props.keepAlive) {
  routerView = [
    h('keep-alive', { props: props.keepAliveProps }, routerView)
  ]
}
return h('transition', {
  props: transitionProps,
  on: listeners
}, routerView)

// nuxt-link
export default {
  name: 'NuxtLink',
  extends: Vue.component('RouterLink'),
}
複製代碼

是擴展了vue-router
其中<nuxt>可接受接收 keep-alive 和 keep-alive-props
<nuxt-link>則是幫咱們擴展了自動預獲取代碼分割頁面
可使用 no-prefetch屬性 禁用

<nuxt-link to="/user" no-prefetch>我的中心</nuxt-link>
複製代碼

自動構建路由

路由在Nuxt中全是自動構建的,這點很是有用,會幫咱們減小不少配置工做
在全部的SSR項目中,其實要作到先後端同構,都是依賴路由來斷定先後端的入口,從而進行輸出的
咱們來看下Nuxt構建路由的規則
簡單來講,我總結了三點:

  • 根據目錄和文件名自動構建同名路由
  • 根據_下劃線構建動態路由
  • 根據vue文件同名文件夾來構建嵌套路由

咱們根據咱們的demo來看看,咱們須要我的中心和列表頁
因此咱們的目錄結構是這樣的

最終編譯的router.js以下:

routes: [{
      path: "/user/login",
      component: _fade0492,
      name: "user-login"
    }, {
      path: "/user/register",
      component: _35d3a876,
      name: "user-register"
    }, {
      path: "/info/:pageIndex?",
      component: _287821ee,
      name: "info-pageIndex"
    }, {
      path: "/",
      component: _48b188ea,
      name: "index"
    }]
複製代碼

能夠看到,列表頁生成了/info/:pageIndex? 這個路由
咱們經過 /info/1或者/info/2 能夠訪問到對應頁碼的數據

咱們再來看看 _pageIndex.vue

<template>
  <div>
    <vmenu type="info" />
    <div class="info-container">
      <div class="info-title">熱門信息</div>
      <ul class="info-content">
        <li v-for="item in list" :key="item.title">
          <section class="info-content-r">{{ item.publishDate }}</section>
          <section class="info-content-l">{{ item.title }}</section>
        </li>
      </ul>
      <vpage :count="count" url="info" :pageIndex="pageIndex" />
    </div>
  </div>
</template>

複製代碼

page.vue

<template>
  <div class="page-container">
    <section>
      <router-link :to="{ name: toUrl, params: { pageIndex: 1 } }">
        第一頁
      </router-link>
    </section>
    <section v-for="(i, ix) in pageCount" :key="i" :class="{ current: pageIndex == ix + 1 }" >
      <router-link :to="{ name: toUrl, params: { pageIndex: ix + 1 } }">
        {{ ix + 1 }}
      </router-link>
    </section>
    <section>
      <router-link :to="{ name: toUrl, params: { pageIndex: pageCount } }">
        最後一頁
      </router-link>
    </section>
  </div>
</template>

<script> export default { name: "page", props: { pageSize: { default: 10 }, pageIndex: { default: 1 }, count: { default: 100 }, url: { default: "" } }, computed: { toUrl() { return this.url + "-pageIndex"; }, pageCount() { return this.count % this.pageSize == 0 ? this.count / this.pageSize : Math.floor(this.count / this.pageSize) + 1; } }, mounted() {}, created() {} }; </script>
複製代碼

能夠看到由於自動構建的路由名稱是 info-pageIndex
因此咱們分頁跳轉的時候也須要對應的拼接好路由

異步數據

異步數據獲取是SSR項目必須的功能,在Nuxt中至關簡便

asyncData

asyncData很眼熟,VUR SSR中也有一樣的方法用來加載異步數據
可是,可是
在Nuxt中的asyncData方法是徹底不同的
在這裏,不只能獲取異步的數據,並且還會將最終的數據掛載到data上
咱們看下demo這裏,從後臺獲取一下新聞列表:

export default {
  async asyncData({
    isDev,
    route,
    store,
    env,
    params,
    query,
    req,
    res,
    redirect,
    error,
    $axios
  }) {
    let data = await $axios.post(`/api/news/list`, {
      page: params.pageIndex || 1
    });
    return { ...data.data };
  },
  components: {
    vmenu: Menu,
    vpage: page
  },
  // router.path是不同的,不用監聽
  // watchQuery: ["pageIndex"],
  data() {
    return {
      pageIndex: this.$route.params.pageIndex || 1
    };
  }
};
複製代碼

這裏axios必定要使用Nuxt官方模塊@nuxt/axios,會自動進行header填充等其餘優化

總之,就是這麼簡單,咱們再async內獲取數據,而後return出來就都OK了
以後組件內就和咱們平時在data內使用數據同樣了

fetch

再來看看fetch,其實fetch就和Vue SSR中的asyncData很像了
它不會綁定在data內,只是用來操做store的 用法以下:

<template>
  <h1>Stars: {{ $store.state.stars }}</h1>
</template>

<script> export default { fetch ({ store, params }) { return axios.get('http://my-api/stars') .then((res) => { store.commit('setStars', res.data) }) } } </script>

複製代碼

相較VUESSR,Nuxt中的異步數據更加純粹,不須要藉助VUEX就能夠實現
store迴歸狀態共享的本質

vuex

咱們再來看看在Nuxt中vuex是怎麼使用的
其實開始也說了,Nuxt約定在store目錄中index.js是默認狀態樹
其餘名稱的文件,會生成對應的store模塊
因此咱們來寫一個保存登陸狀態的store store/user.js

export const state = () => {
    user: null
};

export const mutations = {
    // 設置登陸用戶
    set(state, user) {
        state.user = user;
    },
    loginOut(state) {
        state.user = null;
    }
}
複製代碼

nuxtServerInit

有個問題,咱們何時把登陸狀態寫進store呢?
想一想平時咱們的應用,寫進cookie或者localstorage是比較好的方案
可是咱們SSR項目有個優點,咱們能夠緊密的和後端結合起來
咱們可使用 nuxtServerInit 將客戶的登陸狀態寫進sotre

注意,nuxtServerInit只有服務端調用時纔會執行

store/index.js

export const actions = {
    nuxtServerInit({ commit }, { req }) {
        if (req.session.user) {
            commit("user/set", req.session.user);
        }
    }
}
複製代碼

這樣就能一直維護客戶的最新登陸狀態(固然前端登錄成功後,也要寫入store)

middleware

那麼前端頁面怎麼判斷登陸狀態呢?
在每一個路由切換的時候判斷比較好,好比在router.beforeEach內
可是Nuxt路由是自動構建的,也沒有相關接口
可是別急,咱們有更好用的 middleware
中間件能夠在咱們指定的頁面加載以前執行,而且能夠執行異步執行

佈局頁內能夠設置中間件,這樣全部該佈局的頁面,都會執行該中間件

來看看demo的例子 middleware/default.js

export default function ({ route, store, redirect }) {
    // 若是用戶不存在,跳到登陸頁面
    if (!store.state.user.user)
        redirect('/user/login?redirect=' + route.path);
}

複製代碼

_pageIndex.vue頁面內引用一下該中間件

middleware: "default"
複製代碼

引用以後,若是未登陸就會自動跳轉到登陸頁

模塊/插件

Nuxt有豐富的模塊能夠供咱們使用
官方模塊有:
@nuxt/http: 基於ky-universal的輕量級和通用的HTTP請求
@nuxtjs/axios: 安全和使用簡單Axios與Nuxt.js集成用來請求HTTP
@nuxtjs/pwa: 使用通過嚴格測試,更新且穩定的PWA解決方案來加強Nuxt
@nuxtjs/auth: Nuxt.js的身份驗證模塊,提供不一樣的方案和驗證策略

社區模塊就更多了:
github.com/topics/nuxt…

使用方法也很簡單,直接在nuxt.config.js內 好比:

modules: [
    ['@nuxtjs/axios', { baseURL: 'http://localhost:3000' }],
  ],
複製代碼

固然也還仍是會本身引用一些插件的,咱們可使用plugins配置

plugins: [
     { src: '@/plugins/element-ui', ssr: false }
  ],
複製代碼

注意,若是咱們的插件只能在服務端運行的話,能夠配置ssr:false,而後前端也能夠配合<no-ssr>組件來使用

接着在plugins目錄內進行擴展,這裏就暴露了Vue對象給咱們

import Vue from 'vue'
import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'

Vue.use(Element, { locale })
複製代碼

生命週期

到這裏,咱們的demo頁面就寫完了
要值得注意的是Nuxt生命週期的差別
Nuxt擴展的asyncData,fetch,validate,middleware會在先後端內都執行
nuxtServerInit,serverMiddleware會在服務端執行
暴露給咱們的redirect,error等方法先後端均可以使用,
可是諸如req,res之類的,前端則爲undefined,這也是須要時刻注意的

緩存

最後我們再來看看緩存
組件緩存和VUESSR同樣,咱們在nuxt.config.js內配置緩存項

const LRU = require('lru-cache')
module.exports = {
  render: {
    bundleRenderer: {
      cache: LRU({
        max: 1000,
        maxAge: 1000 * 60 * 15
      })
    }
  }
}

複製代碼

接着組件內指定好serverCacheKey就OK

export default {
  props: ['type'],
  serverCacheKey: props => props.type
}

複製代碼

serverMiddleware

頁面級別的緩存的話,有點特殊,咱們須要使用Nuxt的服務端中間件
這裏咱們再以上次講的redis-lru來作頁面緩存

const config = require('../nuxt.config.js')

module.exports = async function (req, res, next) {
    if (process.env.NODE_ENV === "development") {
        next();
        return;
    }

    const redis = require('redis').createClient(config.redis);
    const lru = require('redis-lru');
    const cache = lru(redis, 100);

    // 獲取緩存的key值
    let key = req.originalUrl
    const value = await cache.get(key)
    if (value) {
        console.log('cached output', key);
        return res.end(value, 'utf-8')
    }
    res._end = res.end
    res.end = async function (data) {
        if (res._headers['content-type'].startsWith('text/html')
            && res.statusCode === 200) {
            await cache.set(key, data)
        }
        res._end(data, 'utf-8')
    }
    next();
}


複製代碼

值得注意的是服務端中間件中的req和res是 Node.js http request 對象
最後輸出須要調用res的原始end方法(就算你Nuxt配置的後端服務器爲koa)

結語

好了,個人VUE SSR系列上下篇都講完了
Nuxt相較VUESSR是一個升級版,若是你是新啓動項目,建議直接從Nuxt開始
若是想體驗一下SSR全流程,能夠從頭開始配置VUESSR,會有不同的體驗

本次Nuxt的demo地址在這裏: github.com/kungithub/s…

相關文章
相關標籤/搜索