最近, 每当组件的内容 (插槽, 子组件等) 发生变化时, 我需要更新它的状态. 对于上下文, 它是一个表单组件, 用于跟踪其输入的有效性状态.
下面的代码片段是以 Options API 格式编写的, 但除了指定的地方外可以在 vue2 和 Vue2 中使用.
开始
先从控制表单状态开始, 根据状态修改一个类, 孩子内容使用填充:
- export default {
- data: () => ({
- isInvalid: false,
- }),
- };
为了更新 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 事件, 表单将捕获该事件.
- export default {
- data: () => ({
- isInvalid: false,
- }),
- methods: {
- validate() {
- // 验证逻辑
- }
- }
- };
验证逻辑可以是简单或复杂的. 本文为了演示, 用简单的方法, 使用 form.checkValidity() API 来查看表单是否基于 html 验证属性而有效.
为了访问 < form > 元素. 可以用 refs 或 $el 属性. 为了简单起见, 本文使用 $el.
- export default {
- data: () => ({
- isInvalid: false,
- }),
- methods: {
- validate() {
- this.isInvalid = !this.$el.checkValidity()
- }
- }
- };
问题
这里有一点问题. 如果表单的内容改变了, 会发生什么? 如果一个 < input > 在表单加载被添加到 DOM 中, 会发生什么?
举个例子, 我们把这个表单组件称为 "MyForm", 在 App 中, 内容如下:
- // App.vue
- v-model="showInput"
- id="toggle-name"
- name="toggle-name"
- type="checkbox"
- />
显示其它 input
Name:
提交
- import Form from "./components/form.vue";
- export default {
- name: "App",
- components: {
- MyForm: Form,
- },
- data: () => ({
- showInput: false,
- }),
- };
当 App.vue 通过条件来隐藏显示某些 input, 我们的表单需要知道. 在这种情况下, 我们会想到在表单内容发生变化时跟踪其有效性, 而不仅仅是在 input 事件或 mounted 生命周期钩子上. 否则, 可能会显示不正确的信息.
熟悉 Vue 的生命周期钩子小伙伴, 这里可能会想到使用 update 来跟踪变化. 理论上, 这听起来不错. 在实践中, 它会创造一个无限的循环, 然后浏览器挂了.
解决方法
经过一番研究和测试, 最佳解决方案是使用 MutationObserver API. 它是浏览器内置的方法, 提供了监视对 DOM 树所做更改的能力, 如果节点的增减, 属性的变动, 文本内容的变动, 这个 API 都可以得到通知.
它是原生的方法, 所以不受限于框架.
使用时, 首先使用 MutationObserver 构造函数, 新建一个观察器实例, 同时指定这个实例的回调函数. 在每次 DOM 变动后调用, 这个回调都被调用. 该回调函数接受两个参数, 第一个是变动数组, 第二个是观察器实例, 将我们的 form 组件改写成如下:
- 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();
- },
- };
这里还需要使用 beforeUnmount 生命周期事件来断开 observer 的连接, 这会清除它所分配的任何内存.
最后, 我们将 isInvalid 状态传递给要访问的内容的插件槽, 这也称作用域的槽, 它非常有用.
- 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();
- },
- };
通过这样的设置, 可以在我们的表单组件中添加任意数量的 input, 并添加任何它需要的条件渲染逻辑. 只要 input 使用 HTML 验证属性, 表单就会跟踪它是否处于有效状态.
此外, 由于使用的是作用域槽, 我们将表单的状态提供给父级, 所以父级可以对有效性的变化做出反应.
例如, 在 App.vue, 我们想在表单无效时 "禁用" 提交按钮, 可以这么来写
- Name:
- type="submit"
- :class="{ disabled: form.isInvalid }"
- >
- Submit
nice~.
希望本文能对你未来的开必有所帮助.
来源: http://developer.51cto.com/art/202109/680536.htm