文章大綱
進入 vue3 後,多了一種跟 react 很像的資料管理方式 provide / inject,和 vuex 相同的是他可以跨組件讀寫資料,但是和 vuex 不同的是跨組件的範圍僅限於 provide 註冊那一層的子組件。
也就是說 provide 類似於 props 把資料傳入子組件,props 只能傳一層,但 provide 可以下去好幾層都可以存取,官方概念圖如下:

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

本文大綱
- provide / nject
 - vuex
 - 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 })'
};
子組件取出來的時候也可以加個 reactive 和 computed 確保你收到的資料可以深層監聽。
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。
這邊的auth、table就是我建立的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()都不會帶出傳入值,其實他有兩個東西在裡面,props、context。其中context裡面就有我們要用的emit。
setup(props, context){
//...
}
//or
setup(props, { attrs, slots, emit }){
//...
}
vue3 這邊會另外要求你有用到的emit要告訴他,不然他分辨不出來XDDD
export default {
  
  emits: ["update:visible"],
  setup(props, { emit }) {  
    //... 
  }
}
對我來說,這三種我都會同時使用在不同情境,所以當你開始迷茫不知道什麼時候該用哪種方式,這篇就可以參考看看囉!





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