preface

This article briefly explores the history of logical composition and reuse patterns for React and Vue, the two main visual libraries: from Mixins to HOC, to Render Props, and finally, more recently, Hooks.

Const {createElement: h} = React; const {createElement: h} = React; Object, instead of ES Modules import. Also, please read the notes carefully!

The full text contains 22,560 words and takes about 45 minutes to read.

Mixins

Mixins in object oriented

Mixins are a popular logic reuse pattern in traditional object-oriented programming, essentially copying properties/methods, as in this example:

const eventMixin = {
  on(type, handler) {
    this.eventMap[type] = handler;
  },
  emit(type) {
    const evt = this.eventMap[type];
    if (typeof evt === 'function') { evt(); }}};class Event {
  constructor() {
    this.eventMap = {}; }}// Copy the mixin attribute methods to the Event prototype object
Object.assign(Event.prototype, eventMixin);

const evt = new Event();
evt.on('click', () = > {console.warn('a'); });
// Trigger the click event after 1 second
setTimeout((a)= > {
  evt.emit('click');
}, 1000);
Copy the code

The Vue mixin

In Vue mixins can contain options that all component instances can pass in, such as Data, computed, and Mounted lifecycle hook functions. Its namesakes merge strategy is that options with values of objects take precedence over component data, lifecycle hook functions of the same name are called, and lifecycle hook functions in mixins are called before components.

const mixin = {
  data() {
    return { message: 'a' };
  },
  computed: {
    msg() { return `msg-The ${this.message}`; }
  },
  mounted() {
    // What do you think will be the printed result of the two property values?
    console.warn(this.message, this.msg); }};new Vue({
  // Why not add a non-empty el option? The mounted lifecycle hook function will not be triggered if the root instance does not have the EL option. You can either set mounted to null or change it to created
  el: '#app'.mixins: [mixin],
  data() {
    return { message: 'b' };
  },
  computed: {
    msg() { return `msg_The ${this.message}`; }
  },
  mounted() {
    // Merge the message property of data, so print b; The same is true for the MSG property, which prints MSG_B
    console.warn(this.message, this.msg); }});Copy the code

As can be seen from mixins’ namesake conflict merge policy, mixins need to do some special processing when adding mixins to components, and adding many mixins will inevitably lead to performance loss.

The React of the mixin

In React, mixins have been removed with the createClass method in version 16, but we can also look at version 15:

React does not automatically merge options with object values. Instead, it reminds developers not to declare properties with the same name
const mixin = {
  // getInitialState() {
  // return { message: 'a' };
  // },
  componentWillMount() {
    console.warn(this.state.message);
    this.setData();
  },
  // setData() {
  // this.setState({ message: 'c' });
  // },
};

const { createElement: h } = React;
const App = React.createClass({
  mixins: [mixin],
  getInitialState() {
    return { message: 'b' }; 
  },
  componentWillMount() {
    // The lifecycle hook function merge strategy is the same for Vue and React: both lifecycle hook functions are called with the same name, and the lifecycle hook function in mixins is called before the component.
    console.warn(this.state.message);
    this.setData();
  },
  setData() {
    this.setState({ message: 'd' });
  },
  render() { return null; }}); ReactDOM.render(h(App),document.getElementById('app'));
Copy the code

The defect of Mixins

  • First, Mixins introduce implicit dependencies, especially when multiple Mixins or even nested Mixins are introduced, and the origin of properties/methods in components is very unclear.
  • Second, Mixins can lead to namespace conflicts, where all introduced Mixins are in the same namespace, and properties/methods introduced by the first mixin are overwritten by properties/methods of the same name from the next mixin, which is especially unfriendly to projects that reference third-party packages
  • Nested Mixins are interdependent and coupled to each other, leading to a snowballing complexity that is not conducive to code maintenance

Well, that’s all for mixins in this article, take a break if you’re a little tired, there’s a lot more to come 🙂

HOC

Higher-order functions

Let’s take a look at higher-order functions and look at the wikipedia concept:

In mathematics and computer science, a higher-order function is one that satisfies at least one of the following conditions: it takes one or more functions as input and outputs one function

The Map function, found in many functional programming languages, is an example of a higher-order function. It takes a function f as an argument and returns a function that takes a list and applies F to each of its elements. In functional programming, a higher-order function that returns another function is called a curried function.

For example (please ignore that I didn’t type check):

function sum(. args) {
  return args.reduce((a, c) = > a + c);
}

const withAbs = fn= >(... args) => fn.apply(null, args.map(Math.abs));
// All arrow functions may be a bit of a burden on the unfamiliar, but it is quite common, and is equivalent to the following
// function withAbs(fn) {
// return (... args) => {
// return fn.apply(null, args.map(Math.abs));
/ /};
// }

const sumAbs = withAbs(sum);
console.warn(sumAbs(1.2.3));
console.warn(sumAbs(1.2 -));
Copy the code

The React of HOC

According to the above concept, a high-order component is a curried function that accepts a component function and outputs a component function. The most classic example of HOC is to wrap a layer of loading state for a component, such as:

For some resources that are loaded slowly, the component displays the standard Loading effect at first. After a certain period of time (for example, 2 seconds), the component changes to a friendly message, such as “The resource is large. Please wait for a moment.

const { createElement: h, Component: C } = React;

// HOC's input can be expressed as simply as this
function Display({ loading, delayed, data }) {
  if (delayed) {
    return h('div'.null.'Resource is large, actively loading, please wait');
  }
  if (loading) {
    return h('div'.null.'Loading');
  }

  return h('div'.null, data);
}
Copy the code
// A high-order component is a curried function that takes a component function and outputs a component function
const A = withDelay()(Display);
const B = withDelay()(Display);

class App extends C {
  constructor(props) {
    super(props);
    this.state = {
      aLoading: true.bLoading: true.aData: null.bData: null};this.handleFetchA = this.handleFetchA.bind(this);
    this.handleFetchB = this.handleFetchB.bind(this);
  }
  
  componentDidMount() {
    this.handleFetchA();
    this.handleFetchB();
  }

  handleFetchA() {
    this.setState({ aLoading: true });
    // The resource will be loaded in 1 second without triggering text switch
    setTimeout((a)= > {
      this.setState({ aLoading: false.aData: 'a' });
    }, 1000);
  }

  handleFetchB() {
    this.setState({ bLoading: true });
    // The resource needs 7 seconds to complete loading, 5 seconds after the start of the request loading prompt text switch
    setTimeout((a)= > {
      this.setState({ bLoading: false.bData: 'b' });
    }, 7000);
  }
  
  render() {
    const {
      aLoading, bLoading, aData, bData,
    } = this.state;
    
    return h('article'.null, [
      h(A, { loading: aLoading, data: aData }),
      h(B, { loading: bLoading, data: bData }),
      // After reloading, the logic for loading prompt text cannot be changed
      h('button', { onClick: this.handleFetchB, disabled: bLoading }, 'click me'),]); }}// By default, the load prompt text will be switched after 5 seconds
function withDelay(delay = 5000) {
  // How to implement this higher order function? Readers can write for themselves
}

ReactDOM.render(h(App), document.getElementById('app'));
Copy the code

It looks something like this:

function withDelay(delay = 5000) {
  return (ComponentIn) = > {
    class ComponentOut extends C {
      constructor(props) {
        super(props);
        this.state = {
          timeoutId: null.delayed: false};this.setDelayTimeout = this.setDelayTimeout.bind(this);
      }

      componentDidMount() {
        this.setDelayTimeout();
      }

      componentDidUpdate(prevProps) {
        // When loading is complete/reloading, clean up the old timer and set the new timer
        if (this.props.loading ! == prevProps.loading) { clearTimeout(this.state.timeoutId);
          this.setDelayTimeout();
        }
      }

      componentWillUnmount() {
        clearTimeout(this.state.timeoutId);
      }

      setDelayTimeout() {
        // After loading is complete/reload needs to reset delayed
        if (this.state.delayed) {
          this.setState({ delayed: false });
        }
        // Set timer only when loading state
        if (this.props.loading) {
          const timeoutId = setTimeout((a)= > {
            this.setState({ delayed: true });
          }, delay);
          this.setState({ timeoutId });
        }
      }
      
      render() {
        const { delayed } = this.state;
        / / passthrough props
        return h(ComponentIn, { ...this.props, delayed });
      }
    }
    
    return ComponentOut;
  };
}
Copy the code

The Vue HOC

The same idea applies to HOC in Vue, except that the input/output component in Vue is not a function or class, but a JavaScript object with the Template /render option:

const A = {
  template: '<div>a</div>'};const B = {
  render(h) {
    return h('div'.null.'b'); }};new Vue({
  el: '#app',
  render(h) {
    // If the first argument to the render function is not a string, it needs to be a JavaScript object with the template/render option
    return h('article'.null, [h(A), h(B)]);
  },
  // To write in a template, you need to register the component in the instance
  // components: { A, B },
  // template: `
  // 
      
// // // / / `, }); Copy the code

Therefore, the input of HOC in Vue needs to be expressed as follows:

const Display = {
  // For brevity, type detection and default Settings are omitted
  props: ['loading'.'data'.'delayed'],
  render(h) {
    if (this.delayed) {
      return h('div'.null.'Resources are too large, trying to load');
    }
    if (this.loading) {
      return h('div'.null.'Loading');
    }

    return h('div'.null.this.data); }};Copy the code
// Use it almost exactly the same way
const A = withDelay()(Display);
const B = withDelay()(Display);

new Vue({
  el: '#app',
  data() {
    return {
      aLoading: true.bLoading: true.aData: null.bData: null}; }, mounted() {this.handleFetchA();
    this.handleFetchB();
  },
  methods: {
    handleFetchA() {
      this.aLoading = true;
      // The resource will be loaded in 1 second without triggering text switch
      setTimeout((a)= > {
        this.aLoading = false;
        this.aData = 'a';
      }, 1000);
    },

    handleFetchB() {
      this.bLoading = true;
      // The resource needs 7 seconds to complete loading, 5 seconds after the start of the request loading prompt text switch
      setTimeout((a)= > {
        this.bLoading = false;
        this.bData = 'b';
      }, 7000);
    },
  },
  render(h) {
    return h('article'.null, [
      h(A, { props: { loading: this.aLoading, data: this.aData } }),
      h(B, { props: { loading: this.bLoading, data: this.bData } }),
      // After reloading, the logic for loading prompt text cannot be changed
      h('button', {
        attrs: {
          disabled: this.bLoading,
        },
        on: {
          click: this.handleFetchB,
        },
      }, 'click me'),]); }});Copy the code

The withDelay function is also easy to write:

function withDelay(delay = 5000) {
  return (ComponentIn) = > {
    return {
      // Props: ComponentIn. Props: ComponentIn
      props: ['loading'.'data'],
      data() {
        return {
          delayed: false.timeoutId: null}; },watch: {
        // Replace componentDidUpdate with watch
        loading(val, oldVal) {
          // When loading is complete/reloading, clean up the old timer and set the new timer
          if(oldVal ! = =undefined) {
            clearTimeout(this.timeoutId);
            this.setDelayTimeout();
          }
        },
      },
      mounted() {
        this.setDelayTimeout();
      },
      beforeDestroy() {
        clearTimeout(this.timeoutId);
      },
      methods: {
        setDelayTimeout() {
          // After loading is complete/reload needs to reset delayed
          if (this.delayed) {
            this.delayed = false;
          }
          // Set timer only when loading state
          if (this.loading) {
            this.timeoutId = setTimeout((a)= > {
              this.delayed = true;
            }, delay);
          }
        },
      },
      render(h) {
        const { delayed } = this;
        / / passthrough props
        return h(ComponentIn, {
          props: {... this.$props, delayed }, }); }}; }; }Copy the code

Nested HOC

Here’s how it’s written: React

const { createElement: h, Component: C } = React;

const withA = (ComponentIn) = > {
  class ComponentOut extends C {
    renderA() {
      return h('p', { key: 'a' }, 'a');
    }
    render() {
      const { renderA } = this;
      return h(ComponentIn, { ...this.props, renderA });
    }
  }

  return ComponentOut;
};

const withB = (ComponentIn) = > {
  class ComponentOut extends C {
    renderB() {
      return h('p', { key: 'b' }, 'b');
    }
    // There is a function with the same name in HOC
    renderA() {
      return h('p', { key: 'c' }, 'c');
    }
    render() {
      const { renderB, renderA } = this;
      return h(ComponentIn, { ...this.props, renderB, renderA });
    }
  }

  return ComponentOut;
};

class App extends C {
  render() {
    const { renderA, renderB } = this.props;
    return h('article'.null[typeof renderA === 'function' && renderA(),
      'app'.typeof renderB === 'function'&& renderB(), ]); }}// What do you think renderA returns? WithA (withB (App)?
const container = withB(withA(App));

ReactDOM.render(h(container), document.getElementById('app'));
Copy the code

Therefore, it is not difficult to see that, for HOC, props also has a naming conflict problem. Similarly, when introducing multiple HOC or even nested HOC, the source of the properties/methods of prop in the component is very unclear

HOC’s strengths and weaknesses

First, defects:

  • First of all, like Mixins, props of HOC also introduce implicit dependencies. When multiple HOC or even nested HOC is introduced, the source of prop properties/methods in components is very unclear
  • Secondly, the props of HOC can cause namespace conflicts, and the properties/methods of prop with the same name can be overridden by HOC that executes later.
  • HOC requires additional component instances to nest to encapsulate logic, resulting in unnecessary performance overhead

Plus advantages:

  • HOC is a pure function with no side effects, and nested HOC does not depend on and couple with each other
  • Output components do not share state with input components and cannot use their ownsetStateDirectly modifying the state of the output component ensures that the state modification comes from a single source.

You might be wondering why HOC hasn’t solved many of the problems caused by Mixins, so why not stick with them?

One important reason is that components defined based on class/function syntax need to be instantiated to copy mixins properties/methods into the component. Developers can copy mixins themselves in the constructor, but it is difficult for the class library to provide such a mixins option.

Well, that’s the end of this article on HOC. This article does not cover the considerations for using HOC /compose functions, but readers unfamiliar with the React documentation are advised to read

Render Props

Render Props in React

You saw the use of Render Props in the nested HOC section above, which essentially means passing Render functions to child components:

const { createElement: h, Component: C } = React;

class Child extends C {
  render() {
    const { render } = this.props;
    return h('article'.null, [
      h('header'.null.'header'),
      typeof render === 'function' && render(),
      h('footer'.null.'footer'),]); }}class App extends C {
  constructor(props) {
    super(props);
    this.state = { loading: false };
  }
  
  componentDidMount() {
    this.setState({ loading: true });
    setTimeout((a)= > {
      this.setState({ loading: false });
    }, 1000);
  }
  renderA() { return h('p'.null.'a'); }
  renderB() { return h('p'.null.'b'); }

  render() {
    const render = this.state.loading ? this.renderA : this.renderB;
    // You don't have to call render, just pass the render function to the child component exactly
    return h(Child, { render });
  }
}

ReactDOM.render(h(App), document.getElementById('app'));
Copy the code

Slot in the Vue

In Vue, the corresponding concept for Render Props is slots or Renderless Components.

const child = {
  template: ` 
      
header
footer
`
.// The rendering function is like this: // render(h) { // return h('article', null, [ // h('header', null, 'header'), // // because no named slot is used, all vNodes are fetched by default // this.$slots.default, // h('footer', null, 'footer'), / /]); // }, }; new Vue({ el: '#app'.components: { child }, data() { return { loading: false }; }, mounted() { this.loading = true; setTimeout((a)= > { this.loading = false; }, 1000); }, template: `

a

b

`
});Copy the code

As you can see in Vue, we don’t need to explicitly pass the render function. The library is passed automatically through $slots.

Slot and slot-scope before Vue2.6 are not described here because of space constraints. Readers can read the official Vue documentation. Here is a description of v-slot:

const child = {
  data() {
    return {
      obj: { name: 'obj'}}; },// The properties bound to slot can be passed to the parent component, which receives' V-slot :[name]="slotProps" '. SlotProps can be named by any other name, or it can be written as object decomposition in the following section
  template: ` 
      
header
footer
`
};new Vue({ el: '#app'.components: { child }, data() { return { loading: false }; }, mounted() { this.loading = true; setTimeout((a)= > { this.loading = false; }, 1000); }, // #content is short for v-slot:content template: ` `});Copy the code

Note that unlike slot, V-slot can only be added to

The strengths and weaknesses of Render Props

As the pattern’s name implies, Render Props is just a use of a component prop. For logical reuse, state/view operations need to be encapsulated in the prop’s Render function, so there is a performance cost as well as HOC. But since prop has only one property, it doesn’t cause HOC Prop name conflicts.

Well, that’s all for this article about Render Props, and finally we’ll cover some of the best component logic combinations and reuse pattern Hooks so far.

Hooks

The Hooks in the React

In React, Hooks were introduced in version 16.8. Let’s look at the action state hook useState:

const { createElement: h, useState } = React;

function App() {
  // No super(props), no this.onclick = this.onclick.bind (this)
  const [count, setCount] = useState(0);

  function onClick() {
    // No this.state.count, no this.setstate
    setCount(count + 1);
  }

  return h('article'.null, [
    h('p'.null, count),
    h('button', { onClick }, 'click me'),]); } ReactDOM.render(h(App),document.getElementById('app'));
Copy the code

There are no lifecycle Hooks in the function, so React Hooks provide useEffect, the callback in the hook will be called after rendering is complete

const { createElement: h, useState, useEffect } = React;

function App() {
  const [message, setMessage] = useState('a');
  const [count, setCount] = useState(0);
  
  useEffect((a)= > {
    // The second argument of 'useEffect' is not specified. Callback will be called after each rendering, so clicking the button will print useEffect forever
    console.warn('use effect', count);
    setTimeout((a)= > {
      setMessage('b');
    }, 1000);

    // useEffect returns functions that will be called after rendering is complete and before the next effect begins
    return (a)= > {
      console.warn('clean up', count);
    };
  });

  useEffect((a)= > {
    // Tell React that your effect doesn't depend on any values in props or state, so it never needs to be executed repeatedly, equivalent to componentDidMount} []);// An empty array can be replaced with an array of variables whose state does not change
  // const [fake] = useState(0);
  // useEffect(() => {}, [fake]);
  
  useEffect((a)= > {
    return (a)= > {
      // equivalent to componentWillUnmount}; } []);console.warn('render', count);

  return h('article'.null, [
    h('p'.null, count),
    h('button', { onClick }, 'click me'),
    h('p'.null, message),
  ]);
}

ReactDOM.render(h(App), document.getElementById('app'));
Copy the code

In addition to the two most commonly used Hooks, React Hooks provide a number of built-in hook functions, such as useCallback:

const { createElement: h, useCallback } = React;

function useBinding(initialValue) {
  const [value, setValue] = useState(initialValue);

  // Two-way binding can be easily implemented with useCallback
  const onChange = useCallback((evt) = > {
    setValue(evt.target.value);
  }, [value]);

  return [value, onChange];
}

function App() {
  const [value, onChange] = useBinding('text');

  return h('article'.null, [
    h('p'.null, value),
    h('input', { value, onChange }),
  ]);
}
Copy the code

Ok, we know the basic usage of Hooks. So how do you rewrite the HOC example from above with Hooks?

For some resources that are loaded slowly, the component displays the standard Loading effect at first. After a certain period of time (for example, 2 seconds), the component changes to a friendly message, such as “The resource is large. Please wait for a moment.

Looking closely at the withDelay function above, it’s not hard to see that at the component level, we just passed a prop named delayed to the input component.

The same is true for Hooks, we can keep the view component Display and root component App unchanged, only modify withDelay HOC to custom HookuseDelay, which only returns the delayed variable.

function useDelay({ loading, delay = 5000 }) {
  // Custom Hook, which returns a delayed variable
}

function HookDisplay(props) {
  const delayed = useDelay({ loading: props.loading });
  See the HOC section in React above for the Display component function
  returnh(Display, { ... props, delayed }); }// Since the two components in this example are identical except for props, they share a component function. (If you look closely at the HOC example, you'll see the same thing.)
const A = HookDisplay;
const B = HookDisplay;
Copy the code
// Can you do what this function does with a more concise function?
function useDelay({ loading, delay = 5000 }) {
  const [delayed, setDelayed] = useState(false);

  useEffect((a)= > {
    // After loading is complete/reload needs to reset delayed
    if (delayed) {
      setDelayed(false);
    }
    // Set timer only when loading state
    const timeoutId = loading ? setTimeout((a)= > {
      setDelayed(true);
    }, delay) : null;

    return (a)= > {
      clearTimeout(timeoutId);
    };
  }, [loading]);

  return delayed;
}
Copy the code

Composition API in Vue

Hooks in Vue are called Composition API proposals, currently the API is not stable, so this is subject to change.

Vue provides two hooks for operating state, ref and reactive(value and state in the previous RFC):

<main id="app">
  <p>{{ count }}</p>
  <button @click="increment">click me</button>
</main>

<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/@vue/composition-api/dist/vue-composition-api.umd.js"></script>

<script>
  / / vueCompositionApi. The default is a install attributes of the object (that is, the Vue plug-in)
  const { ref, default: VueCompositionApi } = vueCompositionApi;
  Vue.use(VueCompositionApi);

  new Vue({
    el: '#app',
    setup() {
      // You'll notice that count is a reactive object with a single value attribute that points to its internal value. Variables declared by ref are called value wrapper objects
      // Wrapper objects are automatically expanded when used in templates, i.e. 'count' can be used directly without writing 'count.value'
      const count = ref(0);
      
      function increment(a) {
        // The very subtle addition of a '. Value 'is one of the reasons Vue decided to rename' value 'to' ref '(the value function returns an object containing the value attribute is not intuitive).
        count.value += 1;
      }

      return{ count, increment }; }});</script>
Copy the code

Note that the Vue ref hook is a bit different from the React useRef hook. The useRef is not an operation state per se (or the state of the operation does not affect the view).

const { createElement: h, useRef } = React;

function App() {
  const count = useRef(0);
  function onClick() {
    // Although the same ref object is returned each time you render, changing the current attribute does not cause the component to re-render
    console.warn(count.current);
    count.current += 1;
  }      

  return h('article'.null, [
    h('p'.null, count.current),
    h('button', { onClick }, 'click me'),]); } ReactDOM.render(h(App),document.getElementById('app'));
Copy the code
<main id="app">
  <p>{{ state.count }}</p>
  <p>{{ state.double }}</p>
  
  <button @click="increment">click me</button>
</main>

<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/@vue/composition-api/dist/vue-composition-api.umd.js"></script>

<script>
  const { default: VueCompositionApi, reactive, computed } = vueCompositionApi;
  Vue.use(VueCompositionApi);

  new Vue({
    el: '#app',
    setup() {
      const state = reactive({
        count: 0.double: computed((a)= > state.count * 2)});// For value properties you can use vue.observable instead
      / / for the computational attributes, vueCompositionApi.com puted the returned object wrapper is a need for processing, the reader can remove annotation printing state. Double check
      // const state = Vue.observable({
      // count: 0,
      // double: computed(() => state.count * 2),
      // });
      function increment() {
        state.count += 1;
      }

      return{ state, increment }; }});</script>
Copy the code

React Hooks are called every time a component is rendered, by implicitly attaching state to the current internal component node and fetching it in order of invocation at the next rendering. Vue setup(), on the other hand, is called only once per component instance at initialization, and the state is stored in the setup() closure by reference.

Therefore, Vue does not provide a hook for operation side effects directly. Instead, Vue provides a hook for life cycle functions.

<main id="app">
  <ul>
    <li v-for="item in list">{{ item }}</li>
  </ul>
</main>

<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/@vue/composition-api/dist/vue-composition-api.umd.js"></script>

<script>
  const { default: VueCompositionApi, reactive, onMounted } = vueCompositionApi;
  Vue.use(VueCompositionApi);

  new Vue({
    el: '#app',
    setup() {
      const list = reactive([1.2.3]);
      onMounted((a)= > {
        setTimeout((a)= > {
          list.push(4);
        }, 1000);
      });

      return{ list }; }});</script>
Copy the code

So the HOC example above is migrated to the Composition API with little modification, leaving the Display component object and root Vue instance options unchanged:

function useDelay(props, delay = 5000) {
  // Custom Hook, which returns a delayed variable
}

const HookDisplay = {
  props: ['loading'.'data'],
  setup(props) {
    const delayed = useDelay(props);
    return { delayed };
  },
  render(h) {
    See the HOC section in Vue above for the Display component object
    return h(Display, {
      props: {
        ...this.$props, delayed: this.delayed, }, }); }};const A = HookDisplay;
const B = HookDisplay;
Copy the code
const {
  default: VueCompositionApi, ref, watch, onMounted, onUnmounted,
} = vueCompositionApi;
Vue.use(VueCompositionApi);

function useDelay(props, delay = 5000) {
  const delayed = ref(false);
  let timeoutId = null;

  // You can try passing the parameter props instead of loading
  // Since loading is a base type, it loses the ability to be responsive when passing parameters (no longer a getter/setter for an object)
  watch((a)= > props.loading, (val, oldVal) => {
    if(oldVal ! = =undefined) { clearTimeout(timeoutId); setDelayTimeout(); }}); onMounted((a)= > {
    setDelayTimeout();
  });
  onUnmounted((a)= > {
    clearTimeout(timeoutId);
  });

  function setDelayTimeout() {
    if (delayed.value) {
      delayed.value = false;
    }
    if (props.loading) {
      timeoutId = setTimeout((a)= > {
        delayed.value = true; }, delay); }}return delayed;
}
Copy the code

The advantage of Hooks

It’s not hard to see that Hooks are a little bit like the idea of Render Props, except that the Render Props are returning components, and Hooks are returning some state (which you need to pass to the component yourself). Thanks to the fine-grained encapsulation capabilities of Hooks, rendering functions no longer need to be passed through components, fixing the bug that Render Props needed additional component instances nested to encapsulate logic.

Well, that’s all for this article on logical composition and reuse patterns. Unavoidably there are omissions and mistakes in the article, but also hope readers to criticize and correct.