天天看点

用这招监听 Vue 的插槽变化

作者:Dmitri Pavlutin

译者:前端小智

来源:Dmitri Pavlutin

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

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

下面的代码片段是以

Options

API格式编写的,但除了指定的地方外可以在Vue2 和 Vue2中使用。

开始

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

<slot/>

填充:

<template>
  <form :class="{ '--invalid': isInvalid }">
    <slot />
  </form>
</template>

<script>
export default {
  data: () => ({
    isInvalid: false,
  }),
};
</script>           

复制

为了更新

isInvalid

属性,我们需要添加一个触发的事件,可以使用

sumit

事件 ,但我更喜用

input

事件。

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

表单不会触发

input

事件,但我们可以使用 "事件委托"。我们将监听器附加到父元素(

<form>

)上,当事件发生在它的子元素(

<input>

<select>

<textarea>

等)上时就会被触发。

任何时候在这个组件的

<slot>

中触发

input

事件,表单将捕获该事件。

<template>
  <form :class="{ '--invalid': isInvalid }" @input="validate">
    <slot />
  </form>
</template>

<script>
export default {
  data: () => ({
    isInvalid: false,
  }),
  methods: {
    validate() {
      // 验证逻辑
    }
  }
};
</script>           

复制

验证逻辑可以是简单或复杂的。本文为了演示,用简单的方法,使用

form.checkValidity()

API 来查看表单是否基于HTML验证属性而有效。

为了访问<form>元素。可以用refs或

<template>
  <form :class="{ '--invalid': isInvalid }" @input="validate">
    <slot />
  </form>
</template>

<script>
export default {
  data: () => ({
    isInvalid: false,
  }),
  methods: {
    validate() {
      this.isInvalid = !this.$el.checkValidity()
    }
  }
};
</script>           

复制

问题

这里有一点问题。如果表单的内容改变了,会发生什么?如果一个

<input>

在表单加载被添加到DOM中,会发生什么?

举个例子,我们把这个表单组件称为

"MyForm"

,在 App 中,内容如下:

// 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>           

复制

App.vue

通过条件来隐藏显示某些

input

,我们的表单需要知道。在这种情况下,我们会想到在表单内容发生变化时跟踪其有效性,而不仅仅是在

input

事件或

mounted

生命周期钩子上。否则,可能会显示不正确的信息。

熟悉 Vue的生命周期钩子小伙伴,这里可能会想到使用

update

来跟踪变化。理论上,这听起来不错。在实践中,它会创造一个无限的循环,然后浏览器挂了。

解决方法

经过一番研究和测试,最佳解决方案是使用

MutationObserver

API。它是浏览器内置的方法,提供了监视对DOM树所做更改的能力,如果节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。

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

使用时,首先使用

MutationObserver

构造函数,新建一个观察器实例,同时指定这个实例的回调函数。在每次 DOM 变动后调用,这个回调都被调用。该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例,将我们的

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

的连接,这会清除它所分配的任何内存。

最后,我们将

isInvalid

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

<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>           

复制

通过这样的设置,可以在我们的表单组件中添加任意数量的

input

,并添加任何它需要的条件渲染逻辑。只要

input

使用HTML验证属性,表单就会跟踪它是否处于有效状态。

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

例如,在

App.vue

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

<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~.

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

~完,我是小智,准备来场按摩,秀起来!

原文:https://austingil.com/watchin...

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

交流

文章每周持续更新,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习