文章大綱
進入 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 }) {
//...
}
}
對我來說,這三種我都會同時使用在不同情境,所以當你開始迷茫不知道什麼時候該用哪種方式,這篇就可以參考看看囉!
發表評論
想要加入討論嗎?請盡情發表您的想法!