使用Vue.js编写抽象组件

来自菜鸟教程
跳转至:导航、​搜索

Vue 组件很棒,对吧? 它们将您的应用程序的视图和行为封装成漂亮的可组合小块。 如果您需要一些额外的功能,只需附加指令! 事情是,指令相当不灵活,不能做所有事情。 例如,指令不能(轻松)发出事件。 嗯,这就是 Vue,当然有解决方案。 抽象组件!

抽象组件与普通组件一样,只是它们不向 DOM 呈现任何内容。 他们只是为现有的行为添加了额外的行为。 您可能熟悉 Vue 内置的抽象组件,例如 <transition><component><slot>

抽象组件的一个很好的用例是跟踪元素何时使用 IntersectionObserver 进入视口。 让我们看一下实现一个简单的抽象组件来处理它。

如果您想要一个适当的生产就绪实现,请查看本教程所基于的 vue-intersect


入门

首先,我们将创建一个简单地呈现其内容的快速抽象组件。 为此,我们将快速了解 渲染函数

IntersectionObserver.vue

export default {
   // Enables an abstract component in Vue.
   // This property is undocumented and may change at any time,
   // but your component should work without it.
  abstract: true,
  // Yay, render functions!
  render() {
    // Without using a wrapper component, we can only render one child component.
    try {
      return this.$slots.default[0];
    } catch (e) {
      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
    }

    return null;
  }
}

恭喜! 你现在有一个抽象组件,好吧,它什么都不做! 它只是渲染它的孩子。

添加 IntersectionObserver

好的,现在让我们坚持 [X39X] 的逻辑。

IntersectionObserver 在 IE 或 Safari 中本机不支持,因此您可能想为它获取 polyfill


IntersectionObserver.vue

export default {
   // Enables an abstract component in Vue.
   // This property is undocumented and may change at any time,
   // but your component should work without it.
  abstract: true,
  // Yay, render functions!
  render() {
    // Without using a wrapper component, we can only render one child component.
    try {
      return this.$slots.default[0];
    } catch (e) {
      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
    }

    return null;
  },

  mounted () {
    // There's no real need to declare observer as a data property,
    // since it doesn't need to be reactive.

    this.observer = new IntersectionObserver((entries) => {
      this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
    });

    // You have to wait for the next tick so that the child element has been rendered.
    this.$nextTick(() => {
      this.observer.observe(this.$slots.default[0].elm);
    });
  }
}

好的,所以现在我们有了一个可以像这样使用的抽象组件:

<intersection-observer @intersect-enter="handleEnter" @intersect-leave="handleLeave">
  <my-honest-to-goodness-component></my-honest-to-goodness-component>
</intersection-observer>

虽然我们还没有完成……

整理起来

当组件从 DOM 中移除时,我们需要确保不会留下任何悬空的 IntersectionObservers,所以现在让我们快速修复这个问题。

IntersectionObserver.vue

export default {
   // Enables an abstract component in Vue.
   // This property is undocumented and may change at any time,
   // but your component should work without it.
  abstract: true,
  // Yay, render functions!
  render() {
    // Without using a wrapper component, we can only render one child component.
    try {
      return this.$slots.default[0];
    } catch (e) {
      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
    }

    return null;
  },

  mounted() {
    // There's no real need to declare observer as a data property,
    // since it doesn't need to be reactive.

    this.observer = new IntersectionObserver((entries) => {
      this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
    });

    // You have to wait for the next tick so that the child element has been rendered.
    this.$nextTick(() => {
      this.observer.observe(this.$slots.default[0].elm);
    });
  },

  destroyed() {
    // Why did the W3C choose "disconnect" as the method anyway?
    this.observer.disconnect();
  }
}

只是为了加分,让我们使用道具使观察者阈值可配置。

IntersectionObserver.vue

export default {
   // Enables an abstract component in Vue.
   // This property is undocumented and may change at any time,
   // but your component should work without it.
  abstract: true,

  // Props work just fine in abstract components!
  props: {
    threshold: {
      type: Array
    }
  },

  // Yay, render functions!
  render() {
    // Without using a wrapper component, we can only render one child component.
    try {
      return this.$slots.default[0];
    } catch (e) {
      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
    }

    return null;
  },

  mounted() {
    // There's no real need to declare observer as a data property,
    // since it doesn't need to be reactive.

    this.observer = new IntersectionObserver((entries) => {
      this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
    }, {
      threshold: this.threshold || 0
    });

    // You have to wait for the next tick so that the child element has been rendered.
    this.$nextTick(() => {
      this.observer.observe(this.$slots.default[0].elm);
    });
  },

  destroyed() {
    // Why did the W3C choose "disconnect" as the method anyway?
    this.observer.disconnect();
  }
}

最终的用法如下所示:

<intersection-observer @intersect-enter="handleEnter" @intersect-leave="handleLeave" :threshold="[0, 0.5, 1]">
  <my-honest-to-goodness-component></my-honest-to-goodness-component>
</intersection-observer>

给你! 你的第一个抽象组件。

非常感谢 Thomas Kjærgaard / Heavyy 最初的 实施和想法