Vue自定義指令的妙用

Vue自定義指令的妙用

在使用 Element 組件庫時,有些需求功能是組件庫不帶有的,使用了自定義指令來解決這些問題

代碼倉庫vue-element-utils
也提供了 npm 包下載使用css

yarn add vue-element-utils
npm install vue-element-utils

入口文件引入:vue

import elementUtils from 'vue-element-utils'

Vue.use(elementUtils);

例如:node

  • Select 滾動加載(懶加載)
  • Dialog 拖拽位置
  • Dialog 拖拽寬高

Vue自定義指令概念

Vue 提供了內置指令 v-model, v-show等,也容許註冊自定義指令。多數狀況中代碼的複用和抽象的主要形式是組件。有的狀況你仍然須要對普通 DOM 元素進行底層操做,這時候就會用到自定義指令。

鉤子函數

  • bind: 只調用一次,指令第一次綁定到元素時調用。這裏能夠進行一次性的初始化設置
  • inserted: 被綁定元素插入父節點時調用(僅保證父節點存在,但不必定已被插入文檔中)
  • update: 所在組件的 VNode 更新時調用
  • componentUpdated: 所在組件的 VNode 及其子 VNode 所有更新後調用
  • unbind: 只調用一次,指令與元素解綁時調用

鉤子函數參數

除了 el以外,其它參數都應該是隻讀的。若是須要在鉤子之間共享數據,建議經過元素的 dataset來進行。
  • el: 指令所綁定的元素,能夠用來直接操做 DOM
  • binding: 一個對象,包含如下 property:git

    • name: 指令名,不包括v-前綴
    • value: 指令的綁定值,例如:v-my-directive="1 + 1"中,綁定值爲2
    • oldValue: 指令綁定的前一個值,僅在updatecomponentUpdated鉤子中可用。不管值是否改變均可用
    • 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 編譯生成的虛擬節點
  • oldVnode: 上一個虛擬節點, 僅在updatecomponentUpdated鉤子中可用

Select 滾動加載(懶加載)

當 Select 組件須要展現的數據很是多的時候,滾動加載能夠做爲一種優化手段,固然還有其它更好的方式,能夠參考Vue項目優化總結github

展現:express

selectScroll

export default {
    bind(el, binding) {
        // 獲取 element-ui 定義好的 scroll 盒子
        const selectWrap = el.querySelector(
            '.el-select-dropdown .el-select-dropdown__wrap'
        );

        selectWrap.addEventListener('scroll', function() {
            /**
             * scrollHeight 獲取元素內容高度(只讀)
             *
             * scrollTop 獲取或者設置元素的偏移量,經常使用於:計算滾動條的位置,當一個元素的容器沒有產生方向的滾動條,那它的 scrollTop 的值默認爲0
             *
             * clientHeight 讀取元素的可見高度(只讀)
             *
             * 若是元素滾動到底, 等式`ele.scrollHeight - ele.scrollTop === ele.clientHeight;`返回 true, 沒有則返回 false
             */
            const condition = this.scrollHeight - this.scrollTop <= this.clientHeight;
            condition && binding.value();
        });
    }
}

Dialog 拖動

展現:npm

drag

export default {
    bind(el) {
        const dialogHeaderEl = el.querySelector('.el-dialog__header');
        const dragDom = el.querySelector('.el-dialog');
        dialogHeaderEl.style.cssText += ';cursor:move;';

        // 獲取原有屬性 ie dom 元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
        const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);

        dialogHeaderEl.onmousedown = (e) => {
            // 鼠標按下,計算當前元素距離可視區的距離
            const disX = e.clientX - dialogHeaderEl.offsetLeft;
            const disY = e.clientY - dialogHeaderEl.offsetTop;

            // 獲取到的值帶 px 正則匹配替換
            let styL, styT;

            // 注意在 ie 中 第一次獲取到的值爲組件自帶 50% 移動以後賦值爲 px
            if (sty.left.includes('%')) {
                styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
                styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
            } else {
                styL = +sty.left.replace(/\px/g, '');
                styT = +sty.top.replace(/\px/g, '');
            }

            document.onmousemove = function(e) {
                // 經過事件委託,計算移動的距離
                const l = e.clientX - disX;
                const t = e.clientY - disY;

                // 移動當前元素
                dragDom.style.left = `${l + styL}px`;
                dragDom.style.top = `${t + styT}px`;

                //將此時的位置傳出去
                //binding.value({x:e.pageX,y:e.pageY})
            };

            document.onmouseup = function(e) {
                document.onmousemove = null;
                document.onmouseup = null;
            };
        };
    }
}

Dialog 拖拽寬度

展現:element-ui

dragWidth

export default {
    bind(el) {
        const dragDom = el.querySelector('.el-dialog');
        const lineEl = document.createElement('div');
        lineEl.style =
            'width: 2px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;';
        lineEl.addEventListener(
            'mousedown',
            function(e) {
                // 鼠標按下,計算當前元素距離可視區的距離
                const disX = e.clientX - el.offsetLeft;

                // 當前寬度
                const curWidth = dragDom.offsetWidth;

                document.onmousemove = function(e) {
                    e.preventDefault(); // 移動時禁用默認事件

                    // 經過事件委託,計算移動的距離
                    const l = e.clientX - disX;

                    dragDom.style.width = `${curWidth + l}px`;
                };

                document.onmouseup = function(e) {
                    document.onmousemove = null;
                    document.onmouseup = null;
                };
            },
            false
        );
        dragDom.appendChild(lineEl);
    }
}

clipboard 剪切板

展現:app

剪切板

const eventUtil = {
    addHandler(element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent('on' + type, handler);
        } else {
            element['on' + type] = handler;
        }
    },
    removeHandler(element, type, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(type, handler, false);
        } else if (element.detachEvent) {
            element.detachEvent('on' + type, handler);
        } else {
            element['on' + type] = null;
        }
    }
};

function clipboard(text) {
    return new Promise((reslove, reject) => {
        const input = document.createElement('input');
        input.setAttribute('readonly', 'readonly');
        input.setAttribute('value', text);
        document.body.appendChild(input);
        input.focus();
        input.setSelectionRange(0, 9999);
        if (document.execCommand('copy')) {
            document.execCommand('copy');
            reslove(text);
        } else {
            reject(new Error('複製失敗'));
        }
        document.body.removeChild(input);
    });
}
const $clipboard = clipboard;

export {
    $clipboard
}

export default {
    bind: function(el, binding, vnode) {
        if (binding.arg === 'success') {
            el._clipboard_success = binding.value;
        } else if (binding.arg === 'error') {
            el._clipboard_error = binding.value;
        } else {
            el._clipboard_message = binding.value;
            eventUtil.addHandler(el, 'click', () => {
                // log(el._clipboard_message);
                clipboard(el._clipboard_message).then(msg => {
                    el._clipboard_success(msg);
                }).catch(err => {
                    el._clipboard_error(err);
                });
            });
        }
    },
    unbind: function (el, binding) {
        if (binding.arg === 'success') {
            delete el._clipboard_success;
        } else if (binding.arg === 'error') {
            delete el._clipboard_error;
        } else {
            delete el._clipboard_message;
            eventUtil.removeHandler(el, 'click');
        }
    }
}

結語

將一些操做 DOM 的方法改成指令方式使用,在 Vue 中能夠大大減輕工做量dom

將一些拓展性高的自定義指令打包到npm上,多個項目均可以使用,減小Ctrl C代碼,且可維護性高,也方便有相同需求的同窗能夠直接使用

相關文章
相關標籤/搜索