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