Usage scenarios

  1. When submitting a form, only after the form is successfully submitted can the next submission be made. When the form is in a network request, it cannot be submitted.
  2. When making a payment, you can only make one payment, also in the network request, cannot submit, etc

Pros (sort of) :

  1. Compared with the operation using anti-shake and throttling, anti-shake and throttling will be affected by the speed of the network request. If the network request is too slow and the time of two clicks exceeds the preset time of anti-shake and throttling, more than one will be triggered. This method captures resolve and reject in promises to be more precise about when a click can be prohibited or released
  2. Instead of using variable controls, there is no need to declare additional variables, just use the deconstructed resolve and Reject controls.
  3. Because disabled and non-disabled use code precision control, there should be many scenarios that apply. So far in the case of network requests, the problem can be solved.

Existing disadvantages:

  1. Maybe you’re not used to using deconstructed stuff

  2. If you are submitting a form and the form is not verified, reject is also used to indicate that you can click on it next time.

    Form validation is just another way of saying that, exactly, as long as the user has clicked, there is no next click or reject. To continue clicking, use Reject. ** So be aware of the reject when using this directive!!

Parameter Description:

Parameters: params, options

  1. Params: Parameter passed in, of any type

    <div class="submit" v-reclick:applyRecruit="{parans: 111}">Apply to join</div>
    <div class="submit" v-reclick:applyRecruit="{parans:'aaa'} ">Apply to join</div>
    <div class="submit" v-reclick:applyRecruit="{parans: {name:'tom',age:16}} ">Apply to join</div>
    Copy the code
  2. Options: reserved configuration item. It is not used for the time being and does not need to be transmitted

Method of use

  1. Put the following functions into the utils public method, or into a JS file

    // h5-better-examples/utils/directive.js
    const handleClick = async (e, binding, vnode) => {
        let target = binding.value || {};
        let {params = null, options = {}} = target;
    
        let flag = Number(e.target.getAttribute('data-click'));
        if (flag) {
            e.preventDefault();
            return;
        }
        let fn = binding.arg;
        if (Object.prototype.toString.call(options) ! = ='[object Object]') {
            throw new Error('Options must be an object');
        }
        if (typeofvnode.context[fn] ! = ='function') {
            throw new Error('V-clickounce bound must be a function');
        }
        // indicate that the element has been clicked
        e.target.setAttribute('data-click'.1);
    
        try {
            await new Promise((resolve, reject) = > vnode.context[fn]({resolve, reject, e, params}));
        } catch (err) {
            console.log(err);
            // If a catch occurs, the asynchronous request failed, so it should be able to click again
            e.target.setAttribute('data-click'.0); }};/** * is the parameter passed by vue2's registration instruction, if necessary, Vue document view properties corresponding to the value of the https://cn.vuejs.org/v2/guide/custom-directive.html * el: click on the node * * vnode binding: vue compile the generated virtual node. * /
    export const setReclickOption = {
        inserted: function(el, binding, vnode) {
            // Initialize with data-click set to 0 to indicate that the element has not been clicked
            el.setAttribute('data-click'.0);
            el.addEventListener('click'.e= > handleClick(e, binding, vnode));
        },
        unbind(el) {
            el.removeEventListener('click', handleClick); }};Copy the code
  2. Import the above setReclickOption in main.js and register it. It can then be used in vue files

    import {setReclickOption} from './utils/directive';
    Vue.directive('clickonce', setReclickOption);
    Copy the code
  3. use

    <div class="submit" v-reclick:applyRecruit= "{parans:{name:' Tom ',age:16}}" >Copy the code
  4. Deconstruct the parameters you need to use

    E: triggered event, same as e in click. Reject/RESOLVE: Controls whether or not you can click next time, but resolve is all you need. Params: parameter passed

     clickResolve({resolve, reject, e, params}) {
        let resolveURL = ' '
         this.$ax.get(resolveURL)
             .then(res= > {
                console.log(e, params);
           }).catch(err= > {
               console.log(err);
               reject();
       });
    },
    Copy the code

Implementation approach

The idea is very simple, that is, when registering a directive, the DOM element that uses the V-reclick event listens for its click event. And add it to the DOMdata-clickProperty, initially set to 0, the data-click tag 0 can be clicked, 1 can not be clicked.

  1. At registration time, initialize data-click to 0 and listen for the DOM click event

    export const setReclickOption = {
        inserted: function(el, binding, vnode) {
            // Initialize with data-click set to 0 to indicate that the element has not been clicked
            el.setAttribute('data-click'.0);
            el.addEventListener('click'.e= > handleClick(e, binding, vnode));
        },
        unbind(el) {
            el.removeEventListener('click', handleClick); }};Copy the code
  2. If the click event is triggered, it determines whether data-click is 0 or 1. If 0, it triggers the function triggered by the click event

    const handleClick = async(e ,  binding) =>{
        // ...
        let flag = Number(e.target.getAttribute('data-click'));
        if (flag) {
            e.preventDefault();
            return;
        }
        // ...
    }
    Copy the code
  3. Before firing, determine whether the parameters passed are correct and immediately set the state to 1

    const handleClick = async(e ,  binding) =>{
        // ...
        
        if (Object.prototype.toString.call(options) ! = ='[object Object]') {
            throw new Error('Options must be an object')}if (typeofbinding.instance[fn] ! = ='function') {
            throw new Error('V-clickounce bound must be a function')}// indicate that the element has been clicked
        e.target.setAttribute('data-click'.1);
        
        // ...
    }
    
    Copy the code
  4. The function is executed in a try… Controllable in catch, according to resolve/reject in Promise. If resolve, data-click is 1 and cannot be clicked. If reject, click again

    const handleClick = async(e ,  binding) =>{
        // ...
        
    	try {
             await new Promise((resolve, reject) = > binding.instance[fn]({resolve, reject, e, params}));
        } catch (err) {
            console.log('Click action failed', err);
    		 e.target.setAttribute('data-click'.0);
        }
        
        // ...
    }
    Copy the code

Instruction registration code (in VUe3)

const handleClick = async(e ,  binding) =>{
    let target = binding.value || {}
    let { params = null, options = {} } = target
    let fn = binding.arg;
    
    let flag = Number(e.target.getAttribute('data-click'));
    if (flag) {
        e.preventDefault();
        return;
    }
    
    if (Object.prototype.toString.call(options) ! = ='[object Object]') {
        throw new Error('Options must be an object')}if (typeofbinding.instance[fn] ! = ='function') {
        throw new Error('V-clickounce bound must be a function')}// indicate that the element has been clicked
    e.target.setAttribute('data-click'.1);

    try {
         await new Promise((resolve, reject) = > binding.instance[fn]({resolve, reject, e, params}));
    } catch (err) {
        console.log('Click action failed', err);
		 e.target.setAttribute('data-click'.0); }}// Register instructions
export const setReclickOption ={
    mounted: function (el, binding) {
        el.setAttribute('data-click'.0);
        el.addEventListener('click'.e= > handleClick(e ,  binding))

    },
    unmounted(el){
        el.removeEventListener('click' ,handleClick )
    }
}
Copy the code

Instruction registration code (in VUe2)

Vue2 does not have the instance attribute (the instance of the component) in its second argument without bind. You need to go to the third argument to get the method in the component’s virtual DOM.

Vue2 uses bind and unbind, vue3 uses mounted and unmounted

// h5-better-examples/utils/directive.js
const handleClick = async (e, binding, vnode) => {
    let target = binding.value || {};
    let {params = null, options = {}} = target;

    let flag = Number(e.target.getAttribute('data-click'));
    if (flag) {
        e.preventDefault();
        return;
    }
    let fn = binding.arg;
    if (Object.prototype.toString.call(options) ! = ='[object Object]') {
        throw new Error('Options must be an object');
    }
    if (typeofvnode.context[fn] ! = ='function') {
        throw new Error('V-clickounce bound must be a function');
    }
    // indicate that the element has been clicked
    e.target.setAttribute('data-click'.1);

    try {
        await new Promise((resolve, reject) = > vnode.context[fn]({resolve, reject, e, params}));
    } catch (err) {
        console.log(err);
        // If a catch occurs, the asynchronous request failed, so it should be able to click again
        e.target.setAttribute('data-click'.0); }};/** * is the parameter passed by vue2's registration instruction, if necessary, Vue document view properties corresponding to the value of the https://cn.vuejs.org/v2/guide/custom-directive.html * el: click on the node * * vnode binding: vue compile the generated virtual node. * /
export const setReclickOption = {
    inserted: function(el, binding, vnode) {
        // Initialize with data-click set to 0 to indicate that the element has not been clicked
        el.setAttribute('data-click'.0);
        el.addEventListener('click'.e= > handleClick(e, binding, vnode));
    },
    unbind(el) {
        el.removeEventListener('click', handleClick); }};Copy the code

Screenshot of bingding parameters

Vnode parameters screenshot