在 Vue 中,父子組件可以透過 props
及 emit
溝通,祖父跟祖孫的溝通則需要透過父來當中間人傳遞。除了 Vuex
外,Provide/Inject
也是一種選擇。
Provide/Inject
在 Vue 2.2.0 中加入,是一種讓上層組件和下層組件可以跨過中間組件來傳遞資料的方式,由上層組件 Provide
,即可 Inject
至下層組件,例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<father></father>
</div>
<script>
const app = Vue.createApp({
provide: {
foo: "bar",
},
});app.component("father", {
template: `<child></child>`,
});app.component("child", {
inject: ["foo"],
template: `<h1>{{ foo }}</h1>`,
});app.mount("#app");
</script>
</body>
</html>
傳遞方式會類似這樣:
需注意的是,Provide
是由父層傳遞資料至子層以下,子層組件 Provide
無法被父層組件 Inject
,例如:
<script>
const app = Vue.createApp({
inject: ["foo"],
});app.component("father", {
provide: {
foo: "bar",
},
template: `<child></child>`,
});app.component("child", {
inject: ["foo"],
template: `<h1>{{ foo }}</h1>`,
});app.mount("#app");
</script>
此時開啟瀏覽器應該會看到:
此外,若祖父層和父層同時 Provide
相同的屬性,子層的 Inject
會以父層為主,例如:
<script>
const app = Vue.createApp({
data() {
return {
foo: "foo provided by grandpa",
};
},
provide() {
return {
foo: this.foo,
};
},
mounted() {
setTimeout(() => {
this.foo = "modified";
}, 2000);
},
});app.component("father", {
template: `<child></child>`,
provide() {
return {
foo: "foo provided by father",
};
},
});app.component("child", {
inject: ["foo"],
template: `<span>Injected foo from child: {{ foo }}</span>`,
});app.mount("#app");
</script>
Provide
提供的值只為其子層以下組件所使用,父層以上、兄弟等其他組件是無法 Inject
的,若在不同組件中有同樣屬性名、但值為不同的情境,感覺會是個不錯的使用契機。
若要傳遞的資料需要透過 this
取得,則要將 provide
改為回傳物件的函式,例如:
<script>
const app = Vue.createApp({
data() {
return {
foo: "bar",
};
},
provide() {
return {
foo: this.foo,
};
},
});app.component("father", {
template: `<child></child>`,
});app.component("child", {
inject: ["foo"],
template: `<h1>{{ foo }}</h1>`,
});app.mount("#app");
</script>
在默認情況下,Provide/Inject 並不是響應式的,在 Vue2 文件有這樣一段敘述(在 Vue3 中則無提及此為故意設計):
Note: the
provide
andinject
bindings are NOT reactive. This is intentional. However, if you pass down an observed object, properties on that object do remain reactive.
大意就是說 Provide/Inject 不是響應式的,我們是刻意這樣設計,但如果傳入的是響應式物件,依然可以是響應式。可參考以下範例(改寫自官方範例)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<todo-list :todos="todos"></todo-list>
<button @click="todos.push('another')">Click to add todo</button>
</div>
<script>
const app = Vue.createApp({
data() {
return {
todos: ["Feed a cat", "Buy tickets"],
};
},
provide() {
return {
todoLength: Vue.computed(() => this.todos.length),
};
},
});app.component("todo-list", {
props: {
todos: {
type: Array,
required: true,
},
},
template: `
<div>
<ol>
<li v-for="(item, index) in todos" :key="index">{{ item }}</li>
</ol>
<todo-list-statistics />
</div>
`,
});app.component("todo-list-statistics", {
inject: ["todoLength"],
created() {
console.log(`Injected property: ${this.todoLength}`);
},
template: `
<p>
todo length: {{ todoLength }}
</p>
`,
});app.mount("#app");
</script>
</body>
</html>
使用上就可以是響應式:
關於 Provide/Inject
就記錄到這,閱讀文件下來給我的感覺有點像是 React
的 createContext
。這個主題其實掛在我的草稿區一陣子了,之前就一直在想等到實際使用過再來記錄,可惜目前大多數跨組件溝通都使用 Vuex
來解決,實際上尚無碰到我自己認為適合使用的情境。
希望這篇記錄能幫助到你,若有任何錯誤,也煩請不吝指出,謝謝。
References: