Vue.jsで抽象コンポーネントを書く
Vueコンポーネントは素晴らしいですよね? これらは、アプリのビューと動作を小さな構成可能な部分にカプセル化します。 それらに少し追加の機能が必要な場合は、ディレクティブを添付するだけです! つまり、ディレクティブはかなり柔軟性がなく、すべてを実行できるわけではありません。 たとえば、ディレクティブは(簡単に)イベントを発行できません。 もちろん、これはVueであり、解決策があります。 抽象的なコンポーネント!
抽象コンポーネントは、DOMに何もレンダリングしないことを除いて、通常のコンポーネントに似ています。 それらは、既存の動作に追加の動作を追加するだけです。 Vueの組み込みコンポーネントからの抽象コンポーネントに精通しているかもしれません。 <transition>
, <component>
、 と <slot>
.
抽象コンポーネントの優れたユースケースは、要素がビューポートに入るタイミングを追跡することです。 IntersectionObserver
. ここでそれを処理するための単純な抽象コンポーネントの実装を見てみましょう。
これを適切に本番環境で実装したい場合は、このチュートリアルのベースとなっているvue-intersectをご覧ください。
入門
まず、コンテンツをレンダリングするだけの簡単な抽象コンポーネントを作成します。 これを実現するために、レンダリング機能について簡単に説明します。
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の追加
さて、今のロジックに固執しましょう IntersectionObserver
.
IntersectionObserver
IEまたはSafariではネイティブにサポートされていないため、polyfillを入手することをお勧めします。
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>
まだ終わっていません…
仕上げ
ぶら下がらないように注意する必要があります IntersectionObservers
コンポーネントがDOMから削除されたら、すぐに修正しましょう。
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();
}
}
そしてボーナスポイントのためだけに、オブザーバーのしきい値を小道具で構成できるようにしましょう。
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>
どうぞ! 最初の抽象コンポーネント。
ThomasKjærgaard/ Heavyy の最初の実装とアイデアに感謝します!