用这招监听 Vue 的插槽变化

开发 前端
最近,每当组件的内容(插槽、子组件等)发生变化时,我需要更新它的状态。对于上下文,它是一个表单组件,用于跟踪其输入的有效性状态。

[[421396]]

 最近,每当组件的内容(插槽、子组件等)发生变化时,我需要更新它的状态。对于上下文,它是一个表单组件,用于跟踪其输入的有效性状态。

下面的代码片段是以Options API格式编写的,但除了指定的地方外可以在Vue2 和 Vue2中使用。

开始

先从控制表单状态开始,根据状态修改一个类,孩子内容使用填充:

  1. <template> 
  2.   <form :class="{ '--invalid': isInvalid }"
  3.     <slot /> 
  4.   </form> 
  5. </template> 
  6.  
  7. <script> 
  8. export default { 
  9.   data: () => ({ 
  10.     isInvalid: false
  11.   }), 
  12. }; 
  13. </script> 

为了更新isInvalid属性,我们需要添加一个触发的事件,可以使用 sumit 事件 ,但我更喜用 input 事件。

  • 表单事件7个: focus, blur, input, select, change, reset, submit 等,具体详解看这篇文章:
  • https://blog.csdn.net/qq_43797996/article/details/103066452

表单不会触发 input 事件,但我们可以使用 "事件委托"。我们将监听器附加到父元素(<form>)上,当事件发生在它的子元素(<input>、<select>、<textarea>等)上时就会被触发。

任何时候在这个组件的<slot>中触发input事件,表单将捕获该事件。

  1. <template> 
  2.   <form :class="{ '--invalid': isInvalid }" @input="validate"
  3.     <slot /> 
  4.   </form> 
  5. </template> 
  6.  
  7. <script> 
  8. export default { 
  9.   data: () => ({ 
  10.     isInvalid: false
  11.   }), 
  12.   methods: { 
  13.     validate() { 
  14.       // 验证逻辑 
  15.     } 
  16.   } 
  17. }; 
  18. </script> 

验证逻辑可以是简单或复杂的。本文为了演示,用简单的方法,使用form.checkValidity() API 来查看表单是否基于HTML验证属性而有效。

为了访问<form>元素。可以用refs或$el属性。为了简单起见,本文使用$el。

  1. <template> 
  2.   <form :class="{ '--invalid': isInvalid }" @input="validate"
  3.     <slot /> 
  4.   </form> 
  5. </template> 
  6.  
  7. <script> 
  8. export default { 
  9.   data: () => ({ 
  10.     isInvalid: false
  11.   }), 
  12.   methods: { 
  13.     validate() { 
  14.       this.isInvalid = !this.$el.checkValidity() 
  15.     } 
  16.   } 
  17. }; 
  18. </script> 

问题

这里有一点问题。如果表单的内容改变了,会发生什么?如果一个<input>在表单加载被添加到DOM中,会发生什么?

举个例子,我们把这个表单组件称为 "MyForm",在 App 中,内容如下:

  1. // App.vue 
  2. <template> 
  3.   <MyForm> 
  4.     <input 
  5.       v-model="showInput" 
  6.       id="toggle-name" 
  7.       name="toggle-name" 
  8.       type="checkbox" 
  9.     /> 
  10.     <label for="toggle-name">显示其它 input</label>  
  11.  
  12.     <template v-if="showInput"
  13.       <label for="name">Name:</label> 
  14.       <input id="name" name="name" required /> 
  15.     </template> 
  16.  
  17.     <button type="submit">提交</button> 
  18.   </MyForm> 
  19. </template> 
  20.  
  21. <script> 
  22. import Form from "./components/form.vue"
  23. export default { 
  24.   name"App"
  25.   components: { 
  26.     MyForm: Form, 
  27.   }, 
  28.   data: () => ({ 
  29.     showInput: false
  30.   }), 
  31. }; 
  32. </script> 

当App.vue通过条件来隐藏显示某些 input,我们的表单需要知道。在这种情况下,我们会想到在表单内容发生变化时跟踪其有效性,而不仅仅是在 input 事件或mounted生命周期钩子上。否则,可能会显示不正确的信息。

熟悉 Vue的生命周期钩子小伙伴,这里可能会想到使用 update 来跟踪变化。理论上,这听起来不错。在实践中,它会创造一个无限的循环,然后浏览器挂了。

解决方法

经过一番研究和测试,最佳解决方案是使用MutationObserver API。它是浏览器内置的方法,提供了监视对DOM树所做更改的能力,如果节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。

它是原生的方法,所以不受限于框架。

使用时,首先使用MutationObserver构造函数,新建一个观察器实例,同时指定这个实例的回调函数。在每次 DOM 变动后调用,这个回调都被调用。该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例,将我们的 form 组件改写成如下:

  1. <template> 
  2.   <form :class="{ '--invalid': isInvalid }" @input="validate"
  3.     <slot /> 
  4.   </form> 
  5. </template> 
  6.  
  7. <script> 
  8. export default { 
  9.   data: () => ({ 
  10.     isInvalid: false
  11.   }), 
  12.   mounted() { 
  13.     const observer = new MutationObserver(this.validate); 
  14.     observer.observe(this.$el, { 
  15.       childList: true
  16.       subtree: true
  17.     }); 
  18.     this.observer = observer; 
  19.   }, 
  20.   methods: { 
  21.     validate() { 
  22.       this.isInvalid = !this.$el.checkValidity(); 
  23.     }, 
  24.   }, 
  25.   beforeUnmount() { 
  26.     this.observer.disconnect(); 
  27.   }, 
  28. }; 
  29. </script> 
  30.  
  31.  
  32. <style scoped> 
  33. </style> 

这里还需要使用 beforeUnmount生命周期事件来断开observer的连接,这会清除它所分配的任何内存。

最后,我们将isInvalid状态传递给要访问的内容的插件槽,这也称作用域的槽,它非常有用。

  1. <template> 
  2.   <form :class="{ '--invalid': isInvalid }" @input="validate"
  3.     <slot v-bind="{ isInvalid }" /> 
  4.   </form> 
  5. </template> 
  6.  
  7. <script> 
  8. export default { 
  9.   data: () => ({ 
  10.     isInvalid: false
  11.   }), 
  12.   mounted() { 
  13.     const observer = new MutationObserver(this.validate); 
  14.     observer.observe(this.$el, { 
  15.       childList: true
  16.       subtree: true
  17.     }); 
  18.     this.observer = observer; 
  19.   }, 
  20.   methods: { 
  21.     validate() { 
  22.       this.isInvalid = !this.$el.checkValidity(); 
  23.     }, 
  24.   }, 
  25.   beforeUnmount() { 
  26.     this.observer.disconnect(); 
  27.   }, 
  28. }; 
  29. </script> 

通过这样的设置,可以在我们的表单组件中添加任意数量的 input,并添加任何它需要的条件渲染逻辑。只要input使用HTML验证属性,表单就会跟踪它是否处于有效状态。

此外,由于使用的是作用域槽,我们将表单的状态提供给父级,所以父级可以对有效性的变化做出反应。

例如,在 App.vue,我们想在表单无效时 "禁用" 提交按钮,可以这么来写

  1. <template> 
  2.   <MyForm> 
  3.     <template slot:default="form"
  4.       <label for="name">Name:</label> 
  5.       <input id="name" name="name" required> 
  6.  
  7.       <button 
  8.         type="submit" 
  9.         :class="{ disabled: form.isInvalid }" 
  10.       > 
  11.         Submit 
  12.       </button> 
  13.     </template> 
  14.   </MyForm> 
  15. </template> 

nice~.

希望本文能对你未来的开必有所帮助。

 

责任编辑:姜华 来源: 今日头条
相关推荐

2023-06-02 07:32:34

localStorage​监听

2020-05-25 17:03:47

Vue嵌套插槽开发

2021-05-08 07:37:32

Vue 命名插槽

2023-12-14 08:25:14

WatchVue.js监听数据

2021-04-14 07:52:00

Vue 作用域插槽

2020-08-10 08:30:35

Vue 数据插槽

2021-12-29 07:51:21

Vue3 插件Vue应用

2016-09-29 08:45:12

vueAPIWeb

2022-07-15 08:45:07

slotVue3

2024-06-03 10:00:51

Vue 3语法插槽

2024-04-10 10:15:16

监听

2022-07-01 12:00:56

Kubernete网络模型

2024-09-10 10:04:47

2019-10-15 09:05:07

域插槽组件前端

2021-04-26 07:53:06

DOM前端框架

2023-07-24 16:28:51

@State@Observed装饰器监听

2013-08-02 15:23:11

2013-08-02 10:39:59

AndroidAPPOS

2021-11-17 08:24:47

Vue3 插件Vue应用

2021-11-22 11:05:20

Vue 3setup前端
点赞
收藏

51CTO技术栈公众号