Journey to the source code
Vue’s custom directive to solve problems that require manipulation of normal DOM elements.
Look at the source code of the Element UI library, where v-infinite-Scroll is configurable for properties:
Source portal: Element & Element-plus
According to my own understanding, I intercepted part of the source code based on vue2.x and added some comments, as follows:
export default {
name: 'InfiniteScroll'./ * * *@description: Calls inserted * when the bound element is inserted into the parent node@param {*} The el directive binds elements that can be used to manipulate the DOM * directly@param {*} Binding directive property object *@param {*} Vnode Vue Virtual node */
inserted(el, binding, vnode) {
// the directive's binding value, then the directive's scrolling callback
const cb = binding.value;
const vm = vnode.context;
// For vertical scrolling, get the target node
const container = getScrollContainer(el, true);
// Get the value of the configured property
const { delay, immediate } = getScrollOptions(el, vm);
// Listen for scroll events with throttling
const onScroll = throttle(delay, handleScroll.bind(el, cb));
// Define the target DOM
el[scope] = { el, vm, container, onScroll };
if (container) {
// Listen for scroll events
container.addEventListener('scroll', onScroll);
// If load is performed immediately (default: true)
if (immediate) {
// To observe changes in the DOM tree structure: create and return a MutationObserver instance
// The onScroll callback is executed when the specified DOM changes
const observer = el[scope].observer = new MutationObserver(onScroll);
// Configure MutationObserver to start observing the target node
observer.observe(container, { childList: true.subtree: true });
// Execute immediately: of course, you have to execute onScroll once, otherwise there will be no change to observeonScroll(); }}},unbind(el) {
const { container, onScroll } = el[scope];
if (container) {
// Remove the listener when called when the directive is unbound from the element
container.removeEventListener('scroll', onScroll); }}};Copy the code
As you can see, the MutationObserver() constructor is used for the infinite-scroll-immediate implementation (supplementary: the element-plus implementation is similar).
Why use MutationObserver
Element’s official documentation notes:
Infinite -scroll-immediate: indicates whether to load the container immediately. Otherwise, the container cannot be filled with content in the initial state. The default is true
In order to solve the problem of scroll loading, the binding value of V-infinite-Scroll is the data load callback function
Official examples:
<template>
<ul class="infinite-list" v-infinite-scroll="load" style="overflow:auto">
<li v-for="i in count" class="infinite-list-item">{{ i }}</li>
</ul>
</template>
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const count = ref(0);
// Used to load the scrolling data
const load = () = > {
count.value += 2;
};
return{ count, load, }; }});</script>
Copy the code
While immediate execution is required to fill the container with content in the initial state, it is necessary to know how the content DOM changes to determine how many times the callback function onScroll needs to be executed
What is MutationObserver
The MutationObserver monitors changes made to the DOM tree. As an observer object, the DOM change executes a trigger callback that provides an interface to manipulate the DOM.
Without further ado, link the portal:
MutationObserver– Web API interface reference
MutationObserverListen for DOM tree changes
- observer.observe(target[, options])
If the source code is changed as follows: an error is reported
Observ (container, {childList: false, subtree: true}); observ (container, {childList: false, subtree: true}); observ (container, {childList: false, subtree: true});Copy the code
[Vue warn]: Error in directive infinite-scroll inserted hook: “TypeError: Failed to execute ‘observe’ on ‘MutationObserver’: The options object must set at least one of ‘attributes’, ‘characterData’, or ‘childList’ to true.”
The options object must set at least one of the “attributes”, “characterData”, or “childList” to true.
- observer.disconnect()
You can stop observing DOM changes, and the callback function is not executed until its observe() method is called again
- Asynchronous execution
The Mutation Observer is a new API defined in DOM4 to replace Mutation Events. It differs from Events in that all listening operations and corresponding processing are performed asynchronously after the execution of other scripts and after all changes have been triggered. That is, if you use an observer to listen for multiple DOM changes, and those DOM changes, the observer will record the changes to the array, wait for them all to be over, and then execute the corresponding callback function from the array once
Other applications
Vue 2.x’s note on asynchronous update queues mentions MutationObserver
Vue internally tries to use native Promise. Then, MutationObserver, and setImmediate for asynchronous queues, and if the execution environment does not support it, setTimeout(fn, 0) is used instead
NextTick (callback) is provided to ensure that after data changes, DOM updates are completed, and then the subsequent operation: callback is performed
For $nextTick source, read Vue 2.x/nextTick
Last but not least
If there is anything wrong, please let me know