進入 vue3 後,多了一種跟 react 很像的資料管理方式 provide / inject,和 vuex 相同的是他可以跨組件讀寫資料,但是和 vuex 不同的是跨組件的範圍僅限於 provide 註冊那一層的子組件。

也就是說 provide 類似於 props 把資料傳入子組件,props 只能傳一層,但 provide 可以下去好幾層都可以存取,官方概念圖如下:

 

可以看一下本人畫的粗淺示意圖,三種資料流的比較:(不要把它當成廣告啊!我自己第一次看也很像某種軟體工具廣告XD)

provide/inject、vuex、props比較
provide/inject、vuex、props比較

本文大綱

  1. provide / nject
  2. vuex
  3. props / emit

 

Provide / Inject

這個新功能算是我覺得 vue3 最讓我覺得改得很好的地方,以前可能只是想多傳一層 props 到第二層或第三層 child component,我就必須寫到 vuex 裡面,但這樣檔案就被分開好遠,還要區分很多module來管理一個小功能的兩三個狀態,寫法很分散;不用 vuex 就是要 props 一層一層傳下去,這種地獄我就不願再提起了。

先來看看怎麼使用吧。

 

前情提要

我會有兩個 component ,一個叫做 grandpa.vue 好了哈哈,另一個叫做 me.vue,所以聰明的你就會知道中間其實還有一層 father.vue,但我們不會用到它,因為我們要把東西直接從 grandpa.vue 傳到 me.vue

 

provide

首先要引入 vue 的 api,並且在setup()中使用:

import { provide } from "vue";
//...

setup() {
  provide("name", "James");
};

這樣我們就在 grandpa.vue 這層建立好 provide 了,provide 第一個丟進去的值就是你的 inject 等等要取出的名稱,取出來之後你就會得到 "James" 的字串。

 

Inject

一樣要先引入 vue 的 api ,然後要在 template 中使用的話,要記得在setup()return出去。

<template>
  我爺爺的名字叫做 {{ name }}
</template>

//...

import { inject } from "vue";

//...

setup(){
  const name = inject("name");
  return { name };
};

vue3 中 template 已經可以不用限制在只能包覆一個子組件了。

 

組合使用

當然我們今天如日中天、日理萬機的事業,絕對不會只讀取一個 name 這麼簡單而已,我們會需要傳入各種資料型態並且還要修改。

這邊要注意的是,要修改你可以使用傳入修改函式,然後在其他地方禁止對 states 直接操作,官網建議的拉~

所以我們會在 states 那邊用 readonly 保護他,只有修改函式可以更動 states 。

import { provide, reactive, readonly } from "vue";

//...

setup() {
  const states = reactive({
    name: "James",
    old: 75,
    //...
  });
  const handleAfterBirthday = () => {
    states.old = states.old + 1
  };
  provide("grandpaStates", readonly(states));
  provide("grandpaDispatch", { handleAfterBirthday })'
};

子組件取出來的時候也可以加個 reactivecomputed 確保你收到的資料可以深層監聽。

import { inject, reactive, computed } from "vue";

//...

setup(){
  const grandpaStates = inject("grandpaStates");
  const { handleAfterBirthday } = inject("grandpaDispatch");
  const states = reactive({
    old: computed(()=> grandpaStates.old),
  });
  
  return { states,  handleAfterBirthday }
};

如此在 template 中可以這樣使用:

<template>
  爺爺今年已經 {{ states.old }} 歲了
  <button @click="handleAfterBirthday">幫爺爺慶生結束</button>
</template>

 

Vuex

在我覺得 vuex 到目前為止還是不可或缺的功能,我的專案即便使用 vue3 ,我還是會把 vuex 用起來,畢竟我覺得比起在最根部 App.vue 那邊用一個帝王級的 provide,vuex 在某些情況下更直觀。(當然也有可能是我的習慣問題XD)

這邊要記得安裝 vuex 4 的版本,因為 vue3 就是要搭配 vuex 4 :

$ yarn add vuex@next --save

 

建立store

建立一個檔案store/index.js

import { createStore } from "vuex";
import auth from "./auth";
import table from "./table";

export default createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: { auth, table },
});

這邊都跟以前差不多,我們一樣要分module,不同的只在創建的 api 函數名稱createStore

這邊的authtable就是我建立的module,當然日後你會增加越來越多。

 

建立module

建立module就跟之前的一模一樣了,提供一個小範例就好:

export const state = {
  user: JSON.parse(sessionStorage.getItem("user")),
};
export const actions = {
};

export const mutations = {
  setUser(state, payload) {
    sessionStorage.setItem("user", JSON.stringify(payload));
    state.user = payload;
  },
};
export const getters = {
  isAuthenticated: state => !!state.user || !!sessionStorage.getItem("user"),
};

export default {
  state,
  actions,
  mutations,
  getters,
  namespaced: true,
};

namespaced請記得放true。我有潔癖拉~你不放也可以,就是大家混在一起而已~

 

在main.js中引入

vue3 的掛載方式就是直接.use就可以了。

import { createApp } from "vue";
import App from "@/App.vue";
import store from "@/store";

const app = createApp(App);
app.use(store);
app.mount("#app");

 

在畫面中使用

vuex4 使用的方式改得跟 vue3 composition api 很像,使用useStore()函式呼叫

import { reactive, computed } from "vue";
import { useStore } from "vuex";

//...

setup(){
  const store = useStore();
  const states = reactive({
    username: computed(()=> store.state.auth.user.username),
  });
  return { states }
}

如果要使用 mutation 的話,需要呼叫 store 裡的 commit

import { reactive, computed } from "vue";
import { useStore } from "vuex";

//...

setup(){
  const store = useStore();
  const handleSetProfile = () => {
    const form = {
      username: "jess103372",
      displayName: "Jessica"
    }
    store.commit("auth/setUser", form);
  }
  return { handleSetProfile }
}

聰明的你應該已經想到,如果我要使用 actions 的話,要怎麼呼叫了吧!

store.dispatch("auth/handleLogout");

 

Props/emit

雖然 props 只能傳一層,但是對於某些控制組件簡單狀態的情況,props 的用法就可以讓程式碼變得很簡潔,然後後面維護的人更容易找到資料源頭。(就是上一層啊XD)

 

前情提要

我自己最常用的情況就是控制要不要顯示彈窗的狀態,彈窗會接收名為visible的 props 來決定要不要顯示彈窗,可是當我點擊彈窗內的關閉按鈕,我要將 props 傳來的 visible 改成 false,但 props 其實是不能被修改的,這時候我們就需要用到 emit 這個功能。

 首先我們要先有一個彈窗的組件、並解我希望點擊按鈕會顯示彈窗:

<template>
  <Dialog v-model:visible="dialogVisible"  />
  <button @click="handleOpenDialog">打開彈窗</button>
</template>

//...

setup(){
  const dialogVisible = ref(false);
  const handleOpenDialog = () => { 
    dialogVisible.value = true;
  }
  return { dialogVisible, handleOpenDialog }
}

當我們點擊按鈕的時候,dialogVisible狀態會被改為true,這時候我們需要在Dialog組件內實作關閉視窗,也就是把dialogVisible狀態改為false

 

彈窗顯示後,就可以點擊到彈窗內的按鈕:

<div class="dialog" v-if="props.visible">
  <button @click="handleClose">關閉彈窗</button>
</div>
<script>
export default {
  name: "Dialog",
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
  },
  emits: ["update:visible"],
  setup(props, { emit }) {
    const handleClose = () => {
      emit("update:visible", false);
    };
    return {
      props,
      handleClose,
    };
  },
};
</script>

這邊的重點是當我們按下按鈕呼叫handleClose函式,他會執行emit功能,第一個參數是我們要改動父層的哪一個 state,因為是要更動,所以前面要加上update:;第二個參數是改成多少?因為我是要關閉彈窗,所以要改成false

那 emit 和 props 從哪裡來的呢?

以往我們使用setup()都不會帶出傳入值,其實他有兩個東西在裡面,propscontext。其中context裡面就有我們要用的emit

setup(props, context){
//...
}

//or

setup(props, { attrs, slots, emit }){
//...
}

 

vue3 這邊會另外要求你有用到的emit要告訴他,不然他分辨不出來XDDD

export default {
  
  emits: ["update:visible"],
  setup(props, { emit }) {  
    //... 
  }

}

 

對我來說,這三種我都會同時使用在不同情境,所以當你開始迷茫不知道什麼時候該用哪種方式,這篇就可以參考看看囉!

4+
0 回復

發表評論

想要加入討論嗎?
請盡情發表您的想法!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。