用這招監(jiān)聽 Vue 的插槽變化
最近,每當(dāng)組件的內(nèi)容(插槽、子組件等)發(fā)生變化時(shí),我需要更新它的狀態(tài)。對(duì)于上下文,它是一個(gè)表單組件,用于跟蹤其輸入的有效性狀態(tài)。
下面的代碼片段是以O(shè)ptions API格式編寫的,但除了指定的地方外可以在Vue2 和 Vue2中使用。
開始
先從控制表單狀態(tài)開始,根據(jù)狀態(tài)修改一個(gè)類,孩子內(nèi)容使用
- <template>
 - <form :class="{ '--invalid': isInvalid }">
 - <slot />
 - </form>
 - </template>
 - <script>
 - export default {
 - data: () => ({
 - isInvalid: false,
 - }),
 - };
 - </script>
 
為了更新isInvalid屬性,我們需要添加一個(gè)觸發(fā)的事件,可以使用 sumit 事件 ,但我更喜用 input 事件。
- 表單事件7個(gè): focus, blur, input, select, change, reset, submit 等,具體詳解看這篇文章:
 - https://blog.csdn.net/qq_43797996/article/details/103066452
 
表單不會(huì)觸發(fā) input 事件,但我們可以使用 "事件委托"。我們將監(jiān)聽器附加到父元素(<form>)上,當(dāng)事件發(fā)生在它的子元素(<input>、<select>、<textarea>等)上時(shí)就會(huì)被觸發(fā)。
任何時(shí)候在這個(gè)組件的<slot>中觸發(fā)input事件,表單將捕獲該事件。
- <template>
 - <form :class="{ '--invalid': isInvalid }" @input="validate">
 - <slot />
 - </form>
 - </template>
 - <script>
 - export default {
 - data: () => ({
 - isInvalid: false,
 - }),
 - methods: {
 - validate() {
 - // 驗(yàn)證邏輯
 - }
 - }
 - };
 - </script>
 
驗(yàn)證邏輯可以是簡(jiǎn)單或復(fù)雜的。本文為了演示,用簡(jiǎn)單的方法,使用form.checkValidity() API 來查看表單是否基于HTML驗(yàn)證屬性而有效。
為了訪問<form>元素??梢杂胷efs或$el屬性。為了簡(jiǎn)單起見,本文使用$el。
- <template>
 - <form :class="{ '--invalid': isInvalid }" @input="validate">
 - <slot />
 - </form>
 - </template>
 - <script>
 - export default {
 - data: () => ({
 - isInvalid: false,
 - }),
 - methods: {
 - validate() {
 - this.isInvalid = !this.$el.checkValidity()
 - }
 - }
 - };
 - </script>
 
問題
這里有一點(diǎn)問題。如果表單的內(nèi)容改變了,會(huì)發(fā)生什么?如果一個(gè)<input>在表單加載被添加到DOM中,會(huì)發(fā)生什么?
舉個(gè)例子,我們把這個(gè)表單組件稱為 "MyForm",在 App 中,內(nèi)容如下:
- // App.vue
 - <template>
 - <MyForm>
 - <input
 - v-model="showInput"
 - id="toggle-name"
 - name="toggle-name"
 - type="checkbox"
 - />
 - <label for="toggle-name">顯示其它 input</label>
 - <template v-if="showInput">
 - <label for="name">Name:</label>
 - <input id="name" name="name" required />
 - </template>
 - <button type="submit">提交</button>
 - </MyForm>
 - </template>
 - <script>
 - import Form from "./components/form.vue";
 - export default {
 - name: "App",
 - components: {
 - MyForm: Form,
 - },
 - data: () => ({
 - showInput: false,
 - }),
 - };
 - </script>
 
當(dāng)App.vue通過條件來隱藏顯示某些 input,我們的表單需要知道。在這種情況下,我們會(huì)想到在表單內(nèi)容發(fā)生變化時(shí)跟蹤其有效性,而不僅僅是在 input 事件或mounted生命周期鉤子上。否則,可能會(huì)顯示不正確的信息。
熟悉 Vue的生命周期鉤子小伙伴,這里可能會(huì)想到使用 update 來跟蹤變化。理論上,這聽起來不錯(cuò)。在實(shí)踐中,它會(huì)創(chuàng)造一個(gè)無限的循環(huán),然后瀏覽器掛了。
解決方法
經(jīng)過一番研究和測(cè)試,最佳解決方案是使用MutationObserver API。它是瀏覽器內(nèi)置的方法,提供了監(jiān)視對(duì)DOM樹所做更改的能力,如果節(jié)點(diǎn)的增減、屬性的變動(dòng)、文本內(nèi)容的變動(dòng),這個(gè) API 都可以得到通知。
它是原生的方法,所以不受限于框架。
使用時(shí),首先使用MutationObserver構(gòu)造函數(shù),新建一個(gè)觀察器實(shí)例,同時(shí)指定這個(gè)實(shí)例的回調(diào)函數(shù)。在每次 DOM 變動(dòng)后調(diào)用,這個(gè)回調(diào)都被調(diào)用。該回調(diào)函數(shù)接受兩個(gè)參數(shù),第一個(gè)是變動(dòng)數(shù)組,第二個(gè)是觀察器實(shí)例,將我們的 form 組件改寫成如下:
- <template>
 - <form :class="{ '--invalid': isInvalid }" @input="validate">
 - <slot />
 - </form>
 - </template>
 - <script>
 - export default {
 - data: () => ({
 - isInvalid: false,
 - }),
 - mounted() {
 - const observer = new MutationObserver(this.validate);
 - observer.observe(this.$el, {
 - childList: true,
 - subtree: true,
 - });
 - this.observer = observer;
 - },
 - methods: {
 - validate() {
 - this.isInvalid = !this.$el.checkValidity();
 - },
 - },
 - beforeUnmount() {
 - this.observer.disconnect();
 - },
 - };
 - </script>
 - <style scoped>
 - </style>
 
這里還需要使用 beforeUnmount生命周期事件來斷開observer的連接,這會(huì)清除它所分配的任何內(nèi)存。
最后,我們將isInvalid狀態(tài)傳遞給要訪問的內(nèi)容的插件槽,這也稱作用域的槽,它非常有用。
- <template>
 - <form :class="{ '--invalid': isInvalid }" @input="validate">
 - <slot v-bind="{ isInvalid }" />
 - </form>
 - </template>
 - <script>
 - export default {
 - data: () => ({
 - isInvalid: false,
 - }),
 - mounted() {
 - const observer = new MutationObserver(this.validate);
 - observer.observe(this.$el, {
 - childList: true,
 - subtree: true,
 - });
 - this.observer = observer;
 - },
 - methods: {
 - validate() {
 - this.isInvalid = !this.$el.checkValidity();
 - },
 - },
 - beforeUnmount() {
 - this.observer.disconnect();
 - },
 - };
 - </script>
 
通過這樣的設(shè)置,可以在我們的表單組件中添加任意數(shù)量的 input,并添加任何它需要的條件渲染邏輯。只要input使用HTML驗(yàn)證屬性,表單就會(huì)跟蹤它是否處于有效狀態(tài)。
此外,由于使用的是作用域槽,我們將表單的狀態(tài)提供給父級(jí),所以父級(jí)可以對(duì)有效性的變化做出反應(yīng)。
例如,在 App.vue,我們想在表單無效時(shí) "禁用" 提交按鈕,可以這么來寫
- <template>
 - <MyForm>
 - <template slot:default="form">
 - <label for="name">Name:</label>
 - <input id="name" name="name" required>
 - <button
 - type="submit"
 - :class="{ disabled: form.isInvalid }"
 - >
 - Submit
 - </button>
 - </template>
 - </MyForm>
 - </template>
 
nice~.
希望本文能對(duì)你未來的開必有所幫助。
















 
 
 










 
 
 
 