[ Nuxt.js 2.x 系列文章 ] Lifecycle 生命週期


版本:nuxt 2.15.8

Nuxt 最大的特點就是 Server Side Render,因此他有獨立的生命週期,來看一下官方提供的圖片:

nuxtServerInit

只在 Nuxt 環境初始化時觸發,當我們想將 server 端資料提前傳給 client 端,可以使用此方法,要注意只能寫在 VueX store/index.js actions

export const state = () => ({
    userInfo: {}
});

export const mutations = {
    setUserInfo(state, value) {
        state.userInfo = value;
    }
};

export const actions = {
    nuxtServerInit({ commit }, { req }) {
        // req.session.user = { name: 'claire' }
        commit('setUserInfo', req.session.user);
    }
};

這樣就可以在 Nuxt 初始化時,觸發 nuxtServerInit 方法,將值傳入 state,我們可以從瀏覽器 Vue 開發者工具看到內容:

如果想將資料傳給其他 VueX modules,可以這樣做:

首先新增一支檔案 store/greeting.js

export const state = () => ({
    message: ''
});

export const mutations = {
    setMessage(state, value) {
        state.message = value;
    }
};

接著在 store/index.js 定義 nuxtServerInit

export const actions = {
    nuxtServerInit({ commit }, { req }) {
        commit('greeting/setMessage', 'Hello World!');
    }
};

這樣就可以觸發 store/greeting.js setMessage 方法,見下圖開發者工具

Route Middleware

中間組件,在頁面渲染前執行,有三種定義方式,執行順序為:Global → Layout → Page

接下來分別說明該如何定義

Global Middleware

在 middleware 資料夾內建立檔案,這裡命名為 global.js

export default ({ from, route, redirect, store, error }) => {
    console.log('global middleware');

    if (!store.isLogin) {
        redirect('/login');
    }
};

接著在 nuxt.config.js 配置

export default {
    router: {
        middleware: [ 'global' ]
    }
}

Layout Middleware

建立 middleware 檔案,這裡命名為 middleware/layout.js,然後配置到任一 layouts 檔案,範例使用 layouts/default.vue

export default {
    name: 'Default',
    middleware: 'layout'
};

或是匿名配置也可以:

export default {
    name: 'Default',
    middleware({ from, route, redirect, store, error }) {
    console.log('layout middleware');
  }
};

Page Middleware

概念同 layouts middleware,配置於任一 pages 檔案,範例使用 pages/about.vue,這裡使用匿名配置來說明

export default {
    name: 'About',
    middleware({ from, route, redirect, store, error }) {
    console.log('page middleware');
  }
};

接著我們從開發者工具查看 console 結果依序為下圖,因此我們可以透過 layout 跟 page middleware 來覆寫 global middleware

validate

於 pages 檔案配置此方法,用來驗證動態路由參數有效性,範例使用 pages/about/_userId.vue

export default {
    name: 'User',
    validate({ params, query }) {
      return true; // 驗證通過
      return false; // 驗證無效,會自動轉導 error page
    }
}

💡 驗證通過必須 return true,否則會自動轉跳 404 error page

asyncData

於 server 端處理非同步的生命週期,在此傳入的內容可以被搜尋引擎爬蟲取得,是提升 SEO 效能的重點生命週期。

只會在頁面載入時調用,由於生命週期在 Vue 之前,因此無法取得 this ,且 asyncData 僅限 pages 底下頁面使用,方法內會自動帶入 context 參數,我們可以安裝 @nuxtjs/axios 套件,axios 會被注入進 context 內,我們可以物件解構方式使用( { $axios, params } = context )(範例使用 pages/about.vue)

export default {
    name: 'About',
    async asyncData({ $axios, params }) {
        const id = params.id;
        const { data } = await $axios.$get(`/api/user/${id}`);
        return {
            userName: data
        };
    }
}

透過 return value,資料被賦予進 Vue 實體,我們透過 this.userName 即可成功取值

<template>
    <div>
        <h1>{{ userName }}</h1>
    </div>
</template>

<script>
  export default {
        name: 'About',
    async asyncData({ $axios, params }) {
      const id = params.id;
            const { data } = await $axios.$get(`/api/user/${id}`);
            return {
                userName: data
            };
    }
  }
</script>

💡 data 如果有相同變數名稱,會在 asyncData 生命週期被複寫,所以除非需要再次修改變數,否則請避免重複命名變數

💡 asyncData 是在 server 端、路由更新前即調用,由於是在瀏覽器渲染前的生命週期,因此無法使用 loading placeholder,也不能使用瀏覽器相關 API

</aside>

fetch

Nuxt v2.12 新增功能,功能類似 asyncData ,在畫面渲染前,同時於 server 端跟 client 端的生命週期,可以使用於任一 .vue 頁面,由於是在 Vue created 之後,因此可以取得 this,初次載入頁面時,fetch 會在 server 端執行,如果是透過 <nuxt-link> 進行路由切換,fetch 在 client 端執行,因此可以在此生命週期加入 loading 效果

💡 <nuxt-link> 為 Nuxt 的路由切換元件,相當於 Vue.js 的 <router-link> ,因此我們只能使用內部連結,外部連結必須使用 <a> 標籤,透過 <nuxt-link> 切換路由,會被視為 SPA 頁面跳轉

以下說明使用方式

<template>
    <ul>
        <li v-for="(post, key) in posts" :key="key">
            {{ post }}
        </li>
    </ul>
</template>

<script>
  export default {
        data() {
      return {
        posts: []
      }
    },
    async fetch() {
            const { data } = await this.$axios.$get('/api/posts');
            this.posts = data;
    }
  }
</script>

如果要重複觸發 fetch 生命週期,可以使用 this.$fetch 來呼叫:

<template>
    <div>
        <ul>
            <li v-for="(post, key) in posts" :key="key">
                {{ post }}
            </li>
        </ul>
        <button @click="$fetch">重新取得貼文</button>
    </div>
</template>

如果我們希望 fetch 只在 client 端運行,可以加上 fetchOnServer: false(預設 true)

取得 fetch 狀態

我們可以透過 this.$fetchState 取得 fetch 當前執行狀態,有以下參數:

pending:Boolean / 是否執行完成,可以在此加入 loading 效果(client 端)

error:null or Error 物件 / 判斷是否發生錯誤

timestamp:整數 / 最後一次執行時間(搭配 activated 使用)

範例:

<template>
    <div>
        <p v-if="$fetchState.pending">Loading...</p>
        <p v-if="$fetchState.error">有東西出錯了</p>
    </div>
</template>

<script>
  export default {
    data() {
      return {
        posts: []
      }
    },
        activated() {
      // 每 30 秒自動呼叫 fetch
      if (this.$fetchState.timestamp <= Date.now() - 30000) {
        this.$fetch()
      }
    },
    async fetch() {
            const { data } = await this.$axios.$get('/api/posts');
            this.posts = data;
    }
  }
</script>

生命週期執行順序

接著我們從瀏覽器開發者工具觀察生命週期執行順序

asyncData() {
    console.log('asyncData');
},
fetch() {
    console.log('fetch');
},
beforeCreate() {
    console.log('beforeCreate');
},
created() {
    console.log('created');
},
beforeMount() {
    console.log('beforeMount');
}

可以發現,createdbeforeCreate 這兩個 Vue.js 生命週期會同時出現在 server 端跟 client 端,如果要避免方法被重複執行,可以這樣做:

  1. 加上 process.client 判斷

     created(){
       if (process.client){
         // 執行內容
       }
     }
    
  2. 使用 Nuxt fetch 生命週期

  3. 使用 Vue beforeMount 生命週期

參考文章:

https://stackoverflow.com/questions/60411436/nuxtjs-page-is-created-twice

https://happy9990929.github.io/2021/09/10/vue-nuxt-lifecycle-hooks/

#Nuxt #nuxt.js #Vue #Vue.js #ssr #lifecycle







你可能感興趣的文章

JavaScript - Destructuring 解構

JavaScript - Destructuring 解構

HTML 基礎

HTML 基礎

原始型別(傳值By Value ) v.s 物件型別(傳參考By Reference)

原始型別(傳值By Value ) v.s 物件型別(傳參考By Reference)






留言討論