使用 Vue.js 编写抽象组件

视图组件很棒,对吗?他们将您的应用程序的视图和行为嵌入到可复制的小块上。如果您需要一些额外的功能,只需添加指令! 事实是,指令相当不灵活,不能做到一切。指令不能(轻松)发出事件,例如。

抽象组件就像正常组件,除非它们对 DOM 没有任何回报,它们只会为现有组件添加额外的行为,您可能熟悉 Vue 内置的抽象组件,例如 <transition>, <component><slot>

抽象组件的一个很好的用例是跟踪当一个元素进入视图端口时,使用IntersectionObserver

<$>[注] 如果你想要一个正确的生产准备的实现,看看 vue-intersect,这本教程是基于。

开始的

首先,我们将创建一个快速抽象的组件,简单地渲染其内容. 为了实现这一点,我们将快速潜入 渲染函数

 1[label IntersectionObserver.vue]
 2export default {
 3   // Enables an abstract component in Vue.
 4   // This property is undocumented and may change at any time,
 5   // but your component should work without it.
 6  abstract: true,
 7  // Yay, render functions!
 8  render() {
 9    // Without using a wrapper component, we can only render one child component.
10    try {
11      return this.$slots.default[0];
12    } catch (e) {
13      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
14    }
15
16    return null;
17  }
18}

恭喜你!你现在有一个抽象的组成部分,好吧,什么都没有!它只是让孩子们。

添加跨界观察员

好了,现在让我们坚持IntersectionObserver的逻辑。

<$>[警告]在IE或Safari中,IntersectionObserver不是本地支持的,所以你可能想为它抓取 polyfill

 1[label IntersectionObserver.vue]
 2export default {
 3   // Enables an abstract component in Vue.
 4   // This property is undocumented and may change at any time,
 5   // but your component should work without it.
 6  abstract: true,
 7  // Yay, render functions!
 8  render() {
 9    // Without using a wrapper component, we can only render one child component.
10    try {
11      return this.$slots.default[0];
12    } catch (e) {
13      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
14    }
15
16    return null;
17  },
18
19  mounted () {
20    // There's no real need to declare observer as a data property,
21    // since it doesn't need to be reactive.
22
23    this.observer = new IntersectionObserver((entries) => {
24      this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
25    });
26
27    // You have to wait for the next tick so that the child element has been rendered.
28    this.$nextTick(() => {
29      this.observer.observe(this.$slots.default[0].elm);
30    });
31  }
32}

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

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

虽然我们还没有完成......

完结

我们需要确保在从DOM中删除组件时不留下任何悬浮的IntersectionObservers,所以现在让我们快速解决这个问题。

 1[label IntersectionObserver.vue]
 2export default {
 3   // Enables an abstract component in Vue.
 4   // This property is undocumented and may change at any time,
 5   // but your component should work without it.
 6  abstract: true,
 7  // Yay, render functions!
 8  render() {
 9    // Without using a wrapper component, we can only render one child component.
10    try {
11      return this.$slots.default[0];
12    } catch (e) {
13      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
14    }
15
16    return null;
17  },
18
19  mounted() {
20    // There's no real need to declare observer as a data property,
21    // since it doesn't need to be reactive.
22
23    this.observer = new IntersectionObserver((entries) => {
24      this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
25    });
26
27    // You have to wait for the next tick so that the child element has been rendered.
28    this.$nextTick(() => {
29      this.observer.observe(this.$slots.default[0].elm);
30    });
31  },
32
33  destroyed() {
34    // Why did the W3C choose "disconnect" as the method anyway?
35    this.observer.disconnect();
36  }
37}

只是为了奖金点,让我们让观察者门槛可用奖励来配置。

 1[label IntersectionObserver.vue]
 2export default {
 3   // Enables an abstract component in Vue.
 4   // This property is undocumented and may change at any time,
 5   // but your component should work without it.
 6  abstract: true,
 7
 8  // Props work just fine in abstract components!
 9  props: {
10    threshold: {
11      type: Array
12    }
13  },
14
15  // Yay, render functions!
16  render() {
17    // Without using a wrapper component, we can only render one child component.
18    try {
19      return this.$slots.default[0];
20    } catch (e) {
21      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
22    }
23
24    return null;
25  },
26
27  mounted() {
28    // There's no real need to declare observer as a data property,
29    // since it doesn't need to be reactive.
30
31    this.observer = new IntersectionObserver((entries) => {
32      this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
33    }, {
34      threshold: this.threshold || 0
35    });
36
37    // You have to wait for the next tick so that the child element has been rendered.
38    this.$nextTick(() => {
39      this.observer.observe(this.$slots.default[0].elm);
40    });
41  },
42
43  destroyed() {
44    // Why did the W3C choose "disconnect" as the method anyway?
45    this.observer.disconnect();
46  }
47}

最终用途看起来是这样的:

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

你的第一个抽象组成部分

谢谢你,谢谢你,谢谢你,谢谢你,谢谢你,谢谢你,谢谢你,谢谢你,谢谢你。

Published At
Categories with 技术
Tagged with
comments powered by Disqus