前言
provide和inject在Vue 2中已經(jīng)被廣泛應(yīng)用,不是新鮮API,3.0重新認(rèn)識(shí)一下它們兩個(gè)。
一句話介紹:provide可以向所有子孫組件提供數(shù)據(jù)以及提供修改數(shù)據(jù)的方法,子孫組件用inject使用數(shù)據(jù)。
在組合式API中的用法
基本用法
似乎,組合式API的provide沒(méi)有提供批量方法, 只能每個(gè)變量寫一句。
頂級(jí)組件:
<template>
<div>
<son />
</div>
</template>
<script setup>
import son from "./son.vue";
import { provide } from "vue";
provide("abc", "123");
</script>
子組件:
<template>
<div>
<grandson />
</div>
</template>
<script setup>
import grandson from "./grandson.vue";
</script>
孫組件:
<template>
<div>我是孫子</div>
</template>
<script setup>
import { inject } from "vue";
const abc = inject("abc");
console.log(abc);
</script>
針對(duì)本文的評(píng)論,說(shuō)明一下
官方說(shuō):
inject() can only be used inside setup() or functional components.
這個(gè)意思是說(shuō),inject()只能放在setup()生命周期里運(yùn)行,不能放在別的周期里運(yùn)行,也不能放在事件周期里運(yùn)行。所以你可以先在setup()周期取值。
例一:這是允許的,因?yàn)閏onsole.log是setup()生命周期里的同步代碼
function xx() {
console.log(inject("abc"))
}
xx()
例二:這是禁止的,因?yàn)閟etTimeout是異步函數(shù),執(zhí)行console.log(inject("abc"))的時(shí)候已經(jīng)不在setup()生命周期里。
function xx() {
setTimeout(() => {
console.log(inject("abc"))
})
}
xx()
例三:讓例一的xx函數(shù)作為鼠標(biāo)事件回調(diào),也是禁止的,原因也一樣。
例四:放在Promise.then()也是禁止的,比如:
Promise.resolve().then(() => {
console.log(inject("abc"))
})
一級(jí)組件修改數(shù)據(jù),孫子組件監(jiān)聽變更
provide的變量必須是響應(yīng)式變量,孫子組件監(jiān)聽的變量也必須是響應(yīng)式變量。
一級(jí)組件:
<template>
<div>
<son />
<button @click="abc = '456'">{{ abc }}</button>
</div>
</template>
<script setup>
import son from "./son.vue";
import { provide } from "vue";
ref: abc = "123";
provide("abc", $abc);
</script>
孫子組件:
<template>
<div>我是孫子 - {{abc}}</div>
</template>
<script setup>
import { inject, watch } from "vue";
ref: abc = inject("abc");
watch($abc, () => {
console.log(abc + '變了');
});
</script>
孫子組件修改數(shù)據(jù),一級(jí)組件監(jiān)聽變更
一級(jí)組件除了提供數(shù)據(jù),還要提供一個(gè)修改數(shù)據(jù)的方法,孫子組件要接收并使用這個(gè)方法,這樣修改的就是一級(jí)組件的數(shù)據(jù),修改之后又會(huì)影響孫子組件的數(shù)據(jù)。
一級(jí)組件:
<template>
<div>
<son />
<button @click="abc = '456'">{{ abc }}</button>
</div>
</template>
<script setup>
import son from "./son.vue";
import { provide } from "vue";
ref: abc = "123";
function updateAbc(val) {
abc = val;
}
provide("abc", $abc);
provide("updateAbc", updateAbc);
</script>
孫子組件:
<template>
<div @click="updateAbc('789')">我是孫子 - {{abc}}</div>
</template>
<script setup>
import { inject, watch } from "vue";
ref: abc = inject("abc");
const updateAbc = inject('updateAbc');
watch($abc, () => {
console.log(abc + '變了');
});
</script>
禁止孫子組件修改一級(jí)組件的數(shù)據(jù)
禁止的話,一級(jí)組件傳遞的變量必須是只讀的,可以是readonly,也可以是shallowRef。這樣孫子組件修改數(shù)據(jù)的話,一級(jí)組件不會(huì)有反應(yīng)。
一級(jí)組件:
<template>
<div>
<son />
<button @click="abc = '456'">{{ abc }}</button>
</div>
</template>
<script setup>
import son from "./son.vue";
import { shallowRef, provide } from "vue";
let abc = shallowRef("123");
function updateAbc(val) {
abc = val;
}
provide("abc", abc);
provide("updateAbc", updateAbc);
</script>
在配置項(xiàng)式API中的用法
跟Vue 2差不多,簡(jiǎn)單說(shuō)一下。
provide
provide語(yǔ)法比在組合式API里要靈活,畢竟可以用對(duì)象。比如:
provide: {
user: 'John Doe',
},
如果要把data里的數(shù)據(jù)provide,說(shuō)白了就是打算用this,provide要寫成函數(shù):
provide() {
return {
user: this.user,
updateUser: this.updateUser
}
},
如果要提供計(jì)算屬性,也是一樣的道理,不過(guò),如果不打算用配置項(xiàng)式計(jì)算屬性,而是想用vue的computed方法,可以寫成:
provide() {
return {
userLength: computed(() => this.user.length),
updateUser: this.updateUser
}
},
inject
inject的用法更簡(jiǎn)單,比如inject: ['user', 'updateUser']。
雙向修改而且響應(yīng)
想要雙向修改而且響應(yīng),就必須借助reactive或者ref家族方法和computed方法。provide的數(shù)據(jù)必須是computed返回的數(shù)據(jù),才能保證數(shù)據(jù)有變化時(shí)刷新孫子組件的數(shù)據(jù)。孫子組件要用computed + unref來(lái)解包裝且計(jì)算屬性,才能得到真正需要的數(shù)據(jù)。
可以看到,由于孫子組件的inject和computed的屬性不能重名,所以我造出一個(gè)abcabc變量名,這就比較麻煩了。我們?cè)倏唇M合式API,就沒(méi)有abcabc這種中間變量,原因是ref: abc = inject("abc");讓接收和加響應(yīng)式一氣呵成,這也是組合式API的優(yōu)勢(shì)之一。
一級(jí)組件:
<template>
<div>
<son />
<button @click="abc = '456'">{{ abc }}</button>
</div>
</template>
<script>
import son from "./son.vue";
import { ref, shallowRef, computed, provide } from "vue";
export default {
components: {
son,
},
data() {
return {
abc: 123,
}
},
provide() {
return {
abcabc: computed(() => this.abc),
updateAbc: this.updateAbc
}
},
methods: {
updateAbc(val) {
this.abc = val;
}
}
}
</script>
孫子組件:
<template>
<div @click="updateAbc('789')">我是孫子 - {{ abc }}</div>
</template>
<script>
import { unref } from "vue";
export default {
inject: ["abcabc", "updateAbc"],
computed: {
abc() {
return unref(this.abcabc);
},
},
};
</script>