Nuxt(3) - store & 生命週期


Posted by TempuraEngineer on 2022-09-30

目錄


client-only component

client-only的用途是使組件只在客戶端渲染

另外也可以透過slot的方式先佔個位置,直到client-only渲染完成

<client-only placeholder='Loading...'>
  <!-- 只在客戶端渲染 -->
  <comments />

  <!-- 在伺服器端渲染,直到client only被掛載好前,會出現這個 -->
  <template #placeholder>
    <comments-placeholder /> 
  </template>
</client-only>


store

store資料夾新增index.js檔可以啟動storeindex.js會被視為是root module

Nuxt會幫你import Vuex,並把store option加到Vue實例

Nuxt的store的state必須是function

// store/index.js

export const state = () => ({// rubyDiamond8
  counter: 0,
})

export const getters = {
  getCounter(state){
      return state.counter;
  },
}

export const mutations = {
  increment(state){
      state.counter++;
  },
}

export const actions = {
  // some code here
}

也可以自己新增module。在store資料夾裡的.js檔會被轉換為具名的模組(namespaced module)

// store/toDos.js

export const state = () => ({
  list: [];
})

export const mutations = {
  add(state, text) {
    state.list.push({
      text,
      done: false
    });

  },
  remove(state, { todo }) {
    state.list.splice(state.list.indexOf(todo), 1);
  },
  toggle(state, todo) {
    todo.done = !todo.done;
  }
}
<template>
  <div class="container">
    <ul>
      <li class="mb-4"><input placeholder="type toDo here" class="p-2" @keyup.enter="addTodo"></li>

      <li v-for="(todo, index) in toDoList" :key="`${todo.text}-${index}`" @click="toggle(todo)">
        <input :checked="todo.done" type="checkbox">
        <span :class="{ 'line-through': todo.done }">{{ todo.text }}</span>
      </li>
    </ul>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  computed: {
    toDoList () {
      return this.$store.state.toDos.list;
    },
  },
  methods: {
    addTodo (e) {
      this.$store.commit('toDos/add', e.target.value);
      e.target.value = '';
    },
    ...mapMutations({
      toggle: 'toDos/toggle'
    }),
  }
}
</script>


Nuxt生命週期

nuxt life

Nuxt的生命週期描述在build後發生的事,至於發生甚麼則根據有沒有開SSR模式而改變,更進一步說會根據SSR或靜態生成改變

伺服器端運行的函式(ex:nuxtServerInit、middleware、validate、asyncData)無法透過this操作Vue實例


server side

(2022/10/5更新)

如果是SSR的話,每次發出initial request就會執行以下的步驟
如果是靜態生成,這些步驟只有在build時每個頁面執行一次

Nuxt生命週期
(Vue本身的生命週期)

  • 伺服器啟動

      nuxt start
    
  • 開始生成

    nuxt generate
    
  • serverMiddleware
    Nuxt內部有connect實例,可以用它註冊一些額外的api route且不須要自己準備伺服器

    先準備好js檔當作後端API

    // /server-middleware/logger.js
    
    const express = require('express');
    const app = express();
    
    app.get('/', function(req, res){
      res.send('Hello Nuxt');
    })
    
    module.exports = app;
    

    在nuxt.config.js設定serverMiddleware

    serverMiddleware是個陣列,裡面可以放字串、物件、function

    // nuxt.config.js
    
    export default {
      serverMiddleware: [ // 
        // path是api路徑,handler是API檔案
        {path:'/test_api', handler:'~/server-middleware/logger'},
      ]
    }
    

    使用axios打API

    <script lang="ts">
    import Vue from 'vue';
    
    import axios from 'axios'
    
    export default Vue.extend({
      name:'Index',
      methods:{
        async axiosGet(){
          const res = await axios.get('/test_api');
    
          console.log(res);
        }
      }
    })
    
    </script>
    

    結果如下

    use axios to request to api

  • server side Nuxt plugin
    依據在nuxt.config.js內的順序跑

  • nuxtServerInit

    其功能是將資料從伺服器傳到客戶端,在universal模式下每次發request都會呼叫它,頁面載入後也會自動被呼叫

    用於Vuex,但只有寫在store/index.js才有用

    第1個引數為Vuex context,第2個為Nuxt context

    // store/index.js
    
    export const state = () => ({
      user: {},
    })
    
    export const mutations = {
      setUser(state, data){
        state.user = JSON.parse(JSON.stringify(data));
      },
    }
    
    export const actions = {
      async nuxtServerInit({commit}) {
        // 把打API的code(非同步)寫在nuxtServerInit時,一定要放在actions
        // 因為mutations必須是同步的
    
        const { name, id } = await fetch('https://api.github.com/users/tempura327').then(response => response.json());
    
        commit('setUser', {
          id,
          profile:`I'm ${name}`,
        });
      },
    }
    
  • middleware
    處理route的hook,渲染頁面、組件前呼叫

    • global middleware
      影響設定在nuxt.config.js裡的route
    • layout middleware
      影響設在layouts的route群組
    • route middleware
      影響設在單個頁面的route
  • validate
    用於驗證router的參數,所以常在動態router的場景出現

    除了SSR的情況下會在頁面初始化時被呼叫,CSR的情況下切換router會呼叫

    這個hook一定要return true或false

    引數是Router和Vuex混合的context

      <script lang="ts">
        import Vue from 'vue';
    
        export default Vue.extend({
          name: 'UserDetail', // _UserDetail.vue是User底下動態router的child
          validate({params}) { 
            // params是動態router帶的參數(ex:User/3123 or User/2210的數字)
            // 只要包含數字就踢到error page
            return !(/^[0-9]+$/.test(params.UserDetail));
          },
        });
      </script>
    
  • asyncData
    只能放在page,其回傳值會被塞到data

      async asyncData(){
        const {id, name} = await fetch('https://api.github.com/users/tempura327').then(response => response.json());
    
        // data裡面會被塞素githubUser這個屬性      
        return { githubUser: {
          id,
          name
        }};
      }
    

在Vue實例被建立前就會被呼叫,所以在這個hook不能使用this,但是可以接收到context,並用context來抓資料,當資料抓好後Nuxt會自行混入data

因為在切換router時asyncData內的promise會被處理好,所以不會需要loading的畫面,但如果需要的話也可以在nuxt.config.js做設定

  loading: { //  loading bar can be used to indicate a loading state to the user
    color: 'blue',
    height: '12px'
  },

資料沒抓好前asyncData會阻擋router的轉換(route navigation),如果發生error則會導到error page

  • (beforeCreate)
  • (created)

  • fetch
    fetch有新舊之分,但都不回傳資料

    新版從2.12開始,本文的fetch是指新的fetch

    在舊fetch只能放page,且無法操作Vue實例。而2.12之後的新fetch可以放在所有component(ex:/pages、/layouts、components/)

    由於fetch在component的Vue實例在伺服器端上建立後才被呼叫,故可以用this操作Vue實例,也因此常用於在打API然後將資料塞到state,讓多個頁面去state取共用資料的情況

    fetch hook的行為可以透過一些屬性來設定

    1. fetchOnServer: boolean,設定在SSR時是否呼叫fetch。如果為false,$fetchState.pending會變true
    2. fetchKey: string | function,當在SSR時key會被用於將fetch得到的結果和data配對(類似mapState的感覺)

    透過$fetchState取得fetch的狀態,方便開關loading、顯示錯誤提示

    父組件

      <template>
        <!-- keep-alive-props設置快取住的葉面的最大數量 -->
        <NuxtChild keep-alive :keep-alive-props="{ max: 10 }"></NuxtChild>
    </template>
    

    子組件

    <script>
        import Vue from 'vue';
    
        export default Vue.extend({
            name: 'Fetch',
            data() {
                return {
                  account:'tempura327',
                }
            },
            async fetch() {
              const res = await fetch(`https://api.github.com/users/${this.account}`)
                                          .then(response => response.json());
    
              this.$store.commit('setUser', res);
            },
            watch: { 
                // 一般來說query string改變時,fetch是不會被呼叫的,但是可以使用watch解決
                '$route.query': '$fetch',
            },
            activated(){
                // 配合NuxtChild 和keep-alive使用,頁面切換再切回來若超過5秒則重新fetch
                if (this.$fetchState.timestamp <= Date.now() - 5000) {
                    this.$fetch();
                }
            },
        })
    </script>
    
  • 將state序列化

  • 渲染HTML
  • HTML檔案生成


client side

(2022/10/5更新)

以下的hook會在瀏覽器上執行,所以不論選擇的模式都會執行

  • (從伺服器端)得到HTML
  • 載入資源
  • client side Nuxt plugin
    依據在nuxt.config.js內的順序跑

  • Vue Hydration
    hydration是指客戶端處理程序
    當Vue掌控了從伺服器端取得的HTML,它會將在客戶端上生成的VNode物件與其混合,以將HTML轉換為動態的DOM
    如此一來變成在客戶端上的資料產生變化時做出反應

    Hydration refers to the client-side process during which Vue takes over the static HTML sent by the server and turns it into dynamic DOM that can react to client-side data changes.

  • middleware
    處理route的hook,渲染頁面、組件前呼叫

    • global middleware
      影響設定在nuxt.config.js裡的route
    • layout middleware
      影響設在layouts的route群組
    • route middleware
      影響設在單個頁面的route
  • asyncData(阻塞)

  • (beforeCreate)
  • (created)
  • fetch(不阻塞)
  • (beforeMount)
  • (mounted)


參考資料

Nuxt - Components
Nuxt - Lifecycle
USING NUXTSERVERINIT IN VUEX TO FETCH COMPONENT DATA
Nuxt - Data Fetching
Nuxt - The validate method
Nuxt - The serverMiddleware property

Client Side Hydration
Day 25. 說好的 window 和 document 呢?


#Nuxt #store #Nuxt生命週期







Related Posts

[ Nuxt.js 2.x 系列文章 ] 搭配 Axios 與自訂 Error Page

[ Nuxt.js 2.x 系列文章 ] 搭配 Axios 與自訂 Error Page

React 基礎

React 基礎

KIOPTRIX: LEVEL 1.1 (#2) 攻略紀錄

KIOPTRIX: LEVEL 1.1 (#2) 攻略紀錄


Comments