6 практических рекомендаций Vue Custom Directive

Vue.js

В Vue 2.0, в дополнение к встроенным директивам по умолчанию для основных функций ( v-model и v-show ), Vue также позволяет регистрировать пользовательские директивы. В Vue2.0 основной формой повторного использования кода и абстракции являются компоненты. Однако бывают случаи, когда вам по-прежнему необходимо выполнять низкоуровневые операции над обычными элементами DOM, и именно здесь используются пользовательские директивы.

Пользовательские директивы Vue имеют два способа глобальной регистрации и локальной регистрации. Путь инструкции по глобальной регистрации, черезVue.directive( id, [definition] )способ регистрации глобальных директив. Если вы хотите зарегистрировать локальную директиву, компонент также принимаетdirectivesОпции.

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

// 注册一个局部自定义指令 `v-focus`
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

Затем мы можем использовать опыт для любого элемента в шаблоне.v-focusсобственности следующим образом:

<input v-focus>

Когда нам нужно зарегистрировать пользовательские директивы в пакетах, напишите много ``Vue.directive(id, [definition]) 会导致代码冗余,所以我们可以利用Функция Vue.use() для завершения пакетной регистрации.

Инструкция по регистрации партии, новаяdirectives/directive.jsдокумент

// 导入指令定义文件
import debounce from './debounce'
import throttle from './throttle'
// 集成一起
const directives = {
  debounce,
  throttle,
}
//批量注册
export default {
  install(Vue) {
    Object.keys(directives).forEach((key) => {
      Vue.directive(key, directives[key])
    })
  },
}

существуетmain.jsимпорт иVue.use()Звоните, чтобы завершить массовую регистрацию.

import Vue from 'vue'
import Directives from './directives/directive.js'
Vue.use(Directives)

Объект определения директивы может предоставлять следующие функции ловушек (все необязательные):

  • . Значение инструкции может измениться, а может и не измениться.但是你可以通过比较更新前后的值来忽略不必要的模板更新
  • componentUpdated: VNode компонента, в котором находится инструкция.и его дочерние VNodesВызывается после всех обновлений.
  • unbind: вызывается только один раз, когда вызывается инструкция и связывается элемент решения.

Далее мы смотрим на параметры функции ловушки (т.е.el,binding,vnodeа такжеoldVnode).

Функция крюка инструкции включена в следующие параметры:

  • el: элемент, к которому привязана директива, который можно использовать для прямого управления DOM.
  • binding: объект, который содержит следующее свойство:
    • name: имя команды, исключаяv-приставка.
    • value: значение привязки директивы, например:v-my-directive="1 + 1", значение привязки равно2.
    • oldValue: Предыдущее значение привязки команды, только вupdateа такжеcomponentUpdatedВ наличии крючки. Независимо от того, доступно ли изменение значения.
    • expression:字符串形式的指令表达式。 Напримерv-my-directive="1 + 1", выражение"1 + 1".
    • arg: Параметры, передаваемые команде, необязательны. Напримерv-my-directive:foo, параметры"foo".
    • modifiers: Объект, который содержит модификатор. Например:v-my-directive.foo.bar, объект модификатора{ foo: true, bar: true }.
  • vnode:Vue 编译生成的虚拟节点。 переехатьVNode APIБольше подробностей.
  • oldVnode: Виртуальный узел, толькоupdateа такжеcomponentUpdatedВ наличии крючки.

Примечание: кромеelКроме того, другие параметры должны быть доступны только для чтения, их нельзя изменять. Если вам нужно обмениваться данными между хуками, рекомендуется передать элементdatasetпродолжать.

Несколько практической акции пользовательские инструкции после Vue

  • команда длительного нажатияv-longpress
  • Функция анти-встряхивания инструкцииv-debounce
  • v-throttle
  • v-click-out
  • v-scroll-pop
  • v-sensor

1 v-longpress

Идеи:

  1. Определите таймер для выполнения функции через n секунд с n в качестве аргумента.
  2. Запускается, когда пользователь нажимает кнопкуmousedownилиtouchstartсобытие, запустить таймер.
  3. еслиclick,mouseup,touchendилиtouchcancelЕсли событие запускается в течение n секунд, таймер сбрасывается и обрабатывается как обычное событие щелчка.
  4. Если таймер не сбрасывается в течение n секунд, это считается долгим нажатием и срабатывает соответствующая функция.
const longpress = {
  bind: function (el, {value:{fn,time}}) {
    //没绑定函数直接返回
    if (typeof fn !== 'function') return
    // 定义定时器变量
    el._timer = null
    // 创建计时器( n秒后执行函数 )
    el._start = (e) => {
      //e.type表示触发的事件类型如mousedown,touchstart等
      //pc端: e.button表示是哪个键按下0为鼠标左键,1为中键,2为右键
      //移动端: e.touches表示同时按下的键为个数
      if (  (e.type === 'mousedown' && e.button && e.button !== 0) || 
            (e.type === 'touchstart' && e.touches && e.touches.length > 1)
      ) return;
      //定时长按n秒后执行事件
      if (el._timer === null) {
        el._timer = setTimeout(() => {
          fn()
        }, time)
        //取消浏览器默认事件,如右键弹窗
        el.addEventListener('contextmenu', function(e) {
          e.preventDefault();
        })
      }
    }
    // 如果两秒内松手,则取消计时器
    el._cancel = (e) => {
      if (el._timer !== null) {
        clearTimeout(el._timer)
        el._timer = null
      }
    }
    // 添加计时监听
    el.addEventListener('mousedown', el._start)
    el.addEventListener('touchstart', el._start)
    // 添加取消监听
    el.addEventListener('click', el._cancel)
    el.addEventListener('mouseout', el._cancel)
    el.addEventListener('touchend', el._cancel)
    el.addEventListener('touchcancel', el._cancel)
  },
  // 指令与元素解绑时,移除事件绑定
  unbind(el) {
    // 移除计时监听
    el.removeEventListener('mousedown', el._start)
    el.removeEventListener('touchstart', el._start)
    // 移除取消监听
    el.removeEventListener('click', el._cancel)
    el.removeEventListener('mouseout', el._cancel)
    el.removeEventListener('touchend', el._cancel)
    el.removeEventListener('touchcancel', el._cancel)
  },
}

export default longpress

Использование: добавить в Домv-longpressи параметры

<template>
  <button v-longpress="{fn: longpress,time:2000}">长按</button>
</template>

<script>
export default {
  methods: {
    longpress () {
      console.log('长按指令生效')
    }
  }
}
</script>

2 v-debounce

Предыстория: В процессе разработки иногда необходимо добавить события мониторинга на полосы ввода или полосы прокрутки, а также необходимо выполнить обработку против сотрясений.

Требования: Предотвратите многократное срабатывание события ввода или прокрутки за короткий период времени и используйте функцию защиты от сотрясений, чтобы ограничить срабатывание по истечении определенного периода времени.

Идеи:

  1. Определите метод для отложенного выполнения.Если метод будет вызван снова в течение времени задержки, время выполнения будет пересчитано.
  2. Привязать событие к переданному методу.
const debounce = {
    inserted: function (el, {value:{fn, event, time}}) {
      //没绑定函数直接返回
      if (typeof fn !== 'function') return
      el._timer = null
      //监听点击事件,限定事件内如果再次点击则清空定时器并重新定时
      el.addEventListener(event, () => {
        if (el._timer !== null) {
          clearTimeout(el._timer)
          el._timer = null
        }
        el._timer = setTimeout(() => {
          fn()
        }, time)
      })
    },
  }
  
  export default debounce
  
  

Использование: добавить в Домv-debounceи функция обратного вызова

<template>
  <input v-debounce="{fn: debounce, event: 'input', time: 5000}" />
      <div v-debounce="{fn: debounce, event: 'scroll', time: 5000}">
          <p>文字文字文字文字...</p>
      </div>
</template>

<script>
export default {
  methods: {
    debounce(){
      console.log('debounce 防抖')
    },
  }
}
</script>

3 v-throttle

Справочная информация: в процессе разработки некоторые кнопки отправки и сохранения иногда нажимаются несколько раз в течение короткого периода времени, что приводит к многократным запросам к внутреннему интерфейсу, что приводит к путанице данных. Например, кнопка «Купить сейчас» будет нажата несколько раз раз Вызовите интерфейс создания заказа.

Требование: не допускать многократного нажатия кнопки в течение короткого периода времени и использовать функцию дроссельной заслонки, чтобы ограничить нажатие только один раз в течение заданного времени.

Идеи:

  1. Определить метод, который управляется переключателем (по умолчанию включен).При первом выполнении функции переключатель выключен.Если метод вызывается в течение указанного времени, он не будет выполняться снова, пока переключатель включается по истечении заданного времени.
  2. Привяжите событие к методу щелчка.
const throttle = {
    bind:function (el,{value:{fn,time}}) {
        if (typeof fn !== 'function') return
        el._flag = true;//开关默认为开
        el._timer = null
        el.handler = function () {
            if (!el._flag) return;
            //执行之后开关关闭
            el._flag && fn()
            el._flag = false
            if (el._timer !== null) {
                clearTimeout(el._timer)
                el._timer = null
            }
            el._timer = setTimeout(() => {
                el._flag = true;//三秒后开关开启
            }, time);
        }
        el.addEventListener('click',el.handler)
    },
    unbind:function (el,binding) {
        el.removeEventListener('click',el.handler)
    }
}

export default throttle

Использование: Добавить в Домv-throttleИ функция обратного вызова может быть.

<template>
 <button v-throttle="{fn: throttle,time:3000}">throttle节流</button>
</template>

<script>
export default {
  methods: {
    throttle () {
      console.log('throttle 节流 只触发一次')
    }
  }
}
</script>

4 v-clickOut

Предыстория: В нашем проекте часто появляется всплывающее окно, нужно кликнуть по всплывающему окну вне всплывающего окна.

Требование: Реализуйте инструкцию, которая щелкает за пределами целевой области, чтобы вызвать указанную функцию.

Идеи:

  1. Определите, является ли выбранный элемент целевым элементом, если да, ничего не делайте, в противном случае активируйте указанную функцию.
const clickOut = {
    bind(el,{value}){
        function clickHandler(e) {
            //先判断点击的元素是否是本身,如果是本身,则返回
            if (el.contains(e.target)) return;
            //判断指令中是否绑定了函数
            if (typeof value === 'function') {
                //如果绑定了函数,则调用函数,此处value就是clickImgOut方法
                value()
            }
        }
        // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
        el.handler = clickHandler;
        //添加事件监听
        setTimeout(() => {
            document.addEventListener('click',el.handler);
        }, 0);
    },
    unbind(el){
        //解除事件监听
        document.removeEventListener('click',el.handler);
    }
}

export default clickOut

Используйте, добавьте элементы, которые должны использовать эту директивуv-click-out

<template>
  <div>
 		<button @click="isImgShow = true">展示弹窗</button>
    <div v-click-out="clickImgOut" v-if="isImgShow" class="pop">
          <img src="https://xxx.jpg" alt="">
          <p>文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字</p>
    </div>
  </div>
</template>

<script>
  export default {
		data(){
      return {
        isImgShow : false
      }
    },
    methods:{
      clickImgOut(){
        this.isImgShow = false;
        console.log('点击弹窗外部')
      }
    }
	}
</script>

5 v-scrollPop

Предыстория: в нашем проекте всплывающие окна часто используются для отображения правил активности.Когда правила активности слишком длинные и их нужно прокручивать, продолжительность вызовет внешнюю прокрутку. В настоящее время в этой ситуации мы можем справиться с этим с помощью глобальных пользовательских инструкций.

Требования: пользовательская команда, которую поп-внутренний контент может прокручивать, внешний не прокручивать.

Идеи:

  1. При отображении всплывающего окна запишите расстояние прокрутки полосы прокрутки, а затем установите фиксированное положение для тела и html, высота — 100%, а верхнее значение — расстояние прокрутки.
  2. Когда всплывающее окно будет закрыто, восстановите исходный стиль и установите исходное значение расстояния прокрутки.
const scrollPop = {
    bind(el) {
        //定义此时到元素的内容垂直滚动的距离
        el.st = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
        let cssStr = `overflow: hidden;width: 100%; height: 100%; position: fixed; top: ${- el.st}px;`
        document.querySelector('html').cssText = cssStr
        document.body.style.cssText = cssStr
    },
    unbind(el,{value}) {
        let cssStr = 'overflow: auto; height: 100%; position: relative; top: 0px;scroll-behavior: auto'
        document.querySelector('html').cssText = cssStr
        document.body.style.cssText = cssStr
        document.querySelector('html').style.scrollBehavior = 'auto'
        //手动设置滚动距离
        document.documentElement.scrollTop = el.st
        document.body.scrollTop = el.st
        if (value !== 'smooth')return;
        //如果传了滚动方式为smooth平稳滚动即有感滚动,当滚动完毕后,把auto改回smooth
        let timer = setTimeout(() => {
            cssStr = `overflow: auto; height: 100%; position: relative; top: 0px; scroll-behavior: ${value||'smooth'}`
            document.querySelector('html').cssText = cssStr
            document.querySelector('html').style.scrollBehavior = value || 'smooth'
            document.body.style.cssText = cssStr
        }, 1);
    }
}

export default scrollPop

Использование: поп необходимость ограничения привязкиv-scroll-popscroll-behaviorстоимость.

<div class="scroll-pop" v-if="isScrollPopShow" v-scroll-pop="'smooth'">
	<div class="content">
    <p>这是很长一段文字,请耐心读完,然后你会发现这段文字并没有什么意义。</p>
    ...
  </div>
</div>

6 v-sensor

  • Popuptrack: всплывающее окно
  • $WebClick: нажмите кнопку страницы
  • PopupBtnClick: нажмите кнопку во всплывающем окне.
  • пользовательское событие

Оптимизация:

1. Пользовательская инструкция v-sensor=" {el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'}"

Зарегистрируйте код, который инкапсулирует пользовательские директивы

const sensor = {
    // 当被绑定的元素插入到 DOM 中时
    inserted: function (el,{value: sensorObj}) {
        let showObj={} ,clickObj={}//showObj代表展示类埋点,clickObj代表点击类埋点
        //如果传入参数格式不为对象,则不向下执行
        if (!Object.prototype.toString.call(sensorObj) === '[object Object]'|| JSON.stringify(sensorObj) == "{}") return
        //遍历传入对象参数,根据key值确定埋点类型
        for (const key in sensorObj) {
            if (Object.hasOwnProperty.call(sensorObj, key)) {
                switch (key) {
                    case 'el':
                        showObj= {
                            name:'ElementShow',
                            value: sensorObj[key]
                        };
                        break;
                    case 'pop':
                        showObj= {
                            name:'PopupTrack',
                            value: sensorObj[key]
                        };
                        break;
                    case 'elClick':
                        clickObj= {
                            name:'$WebClick',
                            value: sensorObj[key]
                        };
                        break;
                    case 'popClick':
                        clickObj= {
                            name:'PopupBtnClick',
                            value: sensorObj[key]
                        };
                        break;  
                    default:
                        break;
                }
            }
        }
        // 展示类埋点执行
        showObj.value && sensors.track(showObj.name, {
            FileName: showObj.value
        });
        //点击类埋点执行
        if (clickObj.value) {
            el.handler = function () {
                clickObj.name === '$WebClick' && sensors.track(clickObj.name, {
                    $element_name: clickObj.value
                });
                clickObj.name === 'PopupBtnClick' && sensors.track(clickObj.name, {
                    FileName: clickObj.value
                });
            }
            el.addEventListener('click',el.handler)
        }
    },
    // 指令与元素解绑的时候,移除事件绑定
    unbind(el) {
        el.handler && el.removeEventListener('click', el.handler)
    }
}
  
  export default sensor
    
  

Для скрытых событий, отличных от пользовательских, лучшим методом оптимизации является использование пользовательских команд. Используйте v-sensor=" {el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'} " . v-sensor получает в качестве параметра объект.Ключом объекта является идентификатор события, а значением объекта является атрибут события.Конкретное соответствие между значениями ключа следующее.

  • EL: ElementShow
  • поп: Попуптрек
  • элклик: $WebClick
  • попклик: Попупбтнклик
//单独使用ElementShow或$WebClick
<div v-sensor="{el :'Btn_XXX_Tag_CXXXon'}">我是一个么得感情的标签</div>
<div v-sensor="{elClick:'Btn_XXX_Tag_Common'}">俺也一样</div>
//ElementShow和$WebClick组合使用方法
<div v-sensor="{el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'}">俺也一样</div>
//单独使用PopupTrack和PopupBtnClick
<div v-sensor="{pop :'Pop_XXX_Tag_Common'}">俺也一样</div>
<div v-sensor="{popClick:'Pop_XXX_Tag_Common'}">俺也一样</div>
//PopupTrack和PopupBtnClick组合使用方法
<div v-sensor="{pop :'Pop_XXX_Tag_Common',popClick:'Pop_XXX_Tag_Common'}">俺也一样</div>
//变量使用方法
<div v-sensor="{pop :`${sensorVal}`}">俺也一样</div>

намекать:

Поскольку пользовательская инструкция выполняется, когда элемент вставляется в DOM страницы, если значение атрибута события использует переменную, завершите операцию в рамках созданного жизненного цикла или привяжите v-if к элементу в качестве соответствующей переменной.

Обмен этой проблемой окончен, спасибо всем~~~