亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么完全掌握Vue自定義指令

發布時間:2023-03-08 11:03:47 來源:億速云 閱讀:90 作者:iii 欄目:開發技術

這篇文章主要介紹“怎么完全掌握Vue自定義指令”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“怎么完全掌握Vue自定義指令”文章能幫助大家解決問題。

    準備:自定義指令介紹

    除了核心功能默認內置的指令 (v-model 和 v-show等),Vue 也允許注冊自定義指令。注意,在 Vue2.0 中,代碼復用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。

    作為使用Vue的開發者,我們對Vue指令一定不陌生,諸如v-model、v-on、v-for、v-if等,同時Vue也為開發者提供了自定義指令的api,熟練的使用自定義指令可以極大的提高了我們編寫代碼的效率,讓我們可以節省時間開心的摸魚~

    試煉:實現v-mymodel

    我的上篇文章說到要自己實現一個v-model指令,這里使用v-myodel模擬一個簡易版的,順便再領不熟悉的同學熟悉一下自定義指令的步驟和注意事項。

    定義指令

    首先梳理思路:原生input控件與組件的實現方式需要區分,input的實現較為簡單,我們先實現一下input的處理。

    首先我們先定義一個不做任何操作的指令

    Vue.directive('mymodel', {
            //只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
            bind(el, binding, vnode, oldVnode) {
            },
            //被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被 插入文檔中),需要父節點dom時使用這個鉤子
            inserted(el, binding, vnode, oldVnode) {
            },
            //所在組件的 VNode 更新時調用,**但是可能發生在其子 VNode 更新之前**。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新 (詳細的鉤子函數參數見下)。
            update(el, binding, vnode, oldVnode) {
            },
            //指令所在組件的 VNode **及其子 VNode** 全部更新后調用。
            componentUpdated(el, binding, vnode, oldVnode) {
            },
            只調用一次,指令與元素解綁時調用。
            unbind(el, binding, vnode, oldVnode) {
            },
    })

    上面的注釋中詳細的說明了各個鉤子函數的調用時機,因為我們是給組件上添加input事件和value綁定,因此我們在bind這個鉤子函數中定義即可。所以我們把其他的先去掉,代碼變成這樣。

    Vue.directive('mymodel', {
            //只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
            bind(el, binding, vnode, oldVnode) { 
            }
    })

    簡單說一下bind函數的幾個回調參數,el是指令綁定組件對應的dom,binding是我們的指令本身,包含name、value、expression、arg等,vnode就是當前綁定組件對應的vnode結點,oldVnode就是vnode更新前的狀態。

    接下來我們要做兩件事:

    • 綁定input事件,同步input的value值到外部

    • value值綁定,監聽value的變化,更新到input的value

    這對于input原生組件比較容易實現:

    //第一步,添加inout事件監聽
    el.addEventListener('input', (e) => {
       //context是input所在的父組件,這一步是同步數據
       vnode.context[binding.expression] = e.target.value;
    })
    //監聽綁定的變量
    vnode.context.$watch(binding.expression, (v) => {
         el.value = v;
    })

    這里解釋一下上面的代碼,vnode.context是什么呢,他就是我們指令所在組件的上下文環境,可以理解就是指令綁定的值所在的組件實例。不熟悉vnode結構的同學建議先看一下官方的文檔,不過文檔描述的比較簡單,不是很全面,所以最好在控制臺log一下vnode的對象看一下它具體的結構,這很有助于我們封裝自定義指令,對理解Vue原理也很有幫助。

    我們可以通過context[binding.expression]獲取v-model上到綁定的值,同樣可以修改它。上面的代碼中我們首先通過在添加的input事件中操作vnode.context[binding.expression] = e.target.value同步input的value值到外部(context),與使用@input添加事件監聽效果是一樣的;然后我們需要做第二件事,做value值的綁定,監聽value的變化,同步值的變更到input的value上,我們想到我們可以使用Vue實例上的額$watch方法監聽值的變化,而context就是那個Vue實例,binding.expression就是我們想要監聽的屬性,如果我們這樣寫

    <input v-mymodel='message'/>

    那么binding.expression就是字符串'message'。所以我們想下面的代碼這樣監聽綁定的響應式數據。

    //監聽綁定的變量
    vnode.context.$watch(binding.expression, (v) => {
         el.value = v;
    })

    至此,input的v-mymodel的處理就完成了(當然input組件還有type為checkbox,radio,select等類型都需要去特別處理,這里就不再一一處理了,感興趣的同學可以自己嘗試去完善一下),但是對于非原生控件的組件,我們要特殊處理。

    因此我們完善代碼如下:

    Vue.directive('mymodel', {
            //只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
            bind(el, binding, vnode, oldVnode) {
               //原生input組件的處理
               if(vnode.tag==='input'){
                    //第一步,添加inout事件監聽
                    el.addEventListener('input', (e) => {
                       //context是input所在的父組件,這一步是同步數據
                       vnode.context[binding.expression] = e.target.value;
                    })
                    //監聽綁定的變量
                    vnode.context.$watch(binding.expression, (v) => {
                         el.value = v;
                    })
               }else{//組件
    
               }
            }
    })

    接下來我們要處理的是自定義組件的邏輯,

    //vnode的結構可以參見文檔。不過我覺得最直觀的方法就是直接在控制臺打印處理
    let {
        componentInstance,
        componentOptions,
        context
    } = vnode;
    const {
       _props
    } = componentInstance;
    //處理model選項
    if (!componentOptions.Ctor.extendOptions.model) {
      componentOptions.Ctor.extendOptions.model = {
            value: 'value',
            event: 'input'
      }
    }
    let modelValue = componentOptions.Ctor.extendOptions.model.value;
    let modelEvent = componentOptions.Ctor.extendOptions.model.event;
    //屬性綁定,這里直接修改了屬性,沒有想到更好的辦法,友好的意見希望可以提出
    _props[modelValue] = binding.value;
    context.$watch(binding.expression, (v) => {
         _props[modelValue] = v;
    })
    //添加事件處理函數,做數據同步
    componentInstance.$on(modelEvent, (v) => {
         context[binding.expression] = v;
    })

    聲明一下,上面的實現不是vue源碼的實現方式,vue源碼中實現v-model更加復雜一點,是結合自定義指令、模板編譯等去實現的,因為我們是應用級別的封裝,所以采用了上述的方式實現。

    實現此v-mymodel需要同學去多了解一下Vnode和Component的API,就像之前說的,最簡單的方法就是直接在控制臺中直接打印出vnode對象,組件的vnode上有Component的實例componentInstance。

    接下來簡單說一下上面的代碼,首先我們可以在componentOptions.Ctor.extendOptions上找到model的定義,如果沒有的話需要設置默認值value和input,然后分別對想原生input的處理一樣,分別監聽binding.expression的變化和modelEvent事件即可。

    需要注意的是,我們上面的代碼直接給_prop做了賦值操作,這實際上是不符合規范的,但是我目前沒有找到更好的方法去實現。

    應用實踐:4個實用的自定義指令

    權限控制

    下面我們定義一個v-permission指令用于全平臺的權限控制

    • role:角色控制;

    • currentUser:當前登錄人判斷;當前用戶是否是業務數據中的創建人或者負責人

    • bussinessStatus:業務狀態判斷;

    • every:與操作;

    • some:或操作;

    示例代碼

    //定義權限類型
    const permissionType = {
        ROLE: 'role',
        CURRENTUSER:'currentUser',
        BUSSINESSSTATUS: 'bussinessStatus',
        MIX_EVERY: 'every',
        MIX_SOME: 'some'
    }
    export default {
        //只調用一次,指令第一次綁定到元素時調用
        bind: function () {
        },
        //當前vdom插入到真實dom時,因為是對dom的樣式操作,在這里操作
        inserted: function (el, binding) {
            let show = false;
            show=processingType(binding.arg,binding.value); 
            el.style.display = `${show ? 'inline-block' : 'none'}`
        },
        //所在組件的VNode更新時調用,狀態更新后需要更新顯示狀態
        update: function (el, binding) {
            //避免無效的模板更新
            if(binding.value===binding.oldValue) return;
            let show = false;
            show=processingType(binding.arg,binding.value); 
            el.style.display = `${show ? 'inline-block' : 'none'}`
        },
        //指令所在組件的 VNode 及其子 VNode 全部更新后
        componentUpdated: function (el, binding) {
        },
        unbind: function () {
        },
    }
    //處理不同類型的權限控制
    function processingType(type,value){
        let values=[];
        switch (type) {
            case permissionType.ROLE:
                return permissionByRole(value);
            case permissionType.CURRENTUSER:
                return permissionCreater(value);
            case permissionType.BUSSINESSSTATUS:
                return permissionBusinessStatus(value);
            case permissionType.MIX_EVERY:
                for(let type in value){
                    values.push(processingType(type,value[type]))
                }
                return values.every(v=>{
                    return v;
                })
            case permissionType.MIX_SOME:
                for(let type in value){
                    values.push(processingType(type,value[type]))
                }
                return values.some(v=>{
                    return v;
                })
            default:
                return false;
        }
    }
    //業務狀態判斷
    function permissionBusinessStatus(bindingValue){
       return bindingValue.status==bindingValue.value;
    }
    //當前用戶?
    function permissionCreater(bindingValue){
        const userInfo = JSON.parse(sessionStorage.CDTPcookie);
        // console.log(userInfo.userInfo.id,bindingValue)
        if(bindingValue instanceof Array){
            return bindingValue.some(v=>{
                return userInfo.userInfo.id==v;
            })
        }
        return userInfo.userInfo.id==bindingValue;
    }
    //角色控制
    export function permissionByRole(bindingValue) {
        //這里也可以是store里的用戶信息
        const userInfo = JSON.parse(sessionStorage.userInfo);  
        let roles = []
        if (userInfo) {
            roles = userInfo.roleList
        }
        let show = false;
        if (bindingValue instanceof Array) {
            return roles.some(role => {//多角色處理
                return bindingValue.some(item => {
                    return role.roleCode === item
                })
            })
        } else if (typeof bindingValue == 'string') {
            show = roles.some(role => {
                return role.roleCode === bindingValue;
            })
        }
        return show;
    }

    簡單說一下上面????指令的定義思路和使用方法。整體思路就是通過processingType處理權限邏輯,使用el.style.display控制組件顯示或隱藏。我在這里從日常應用中提取了一些通用的processingType中的權限處理方式,方便大家理解也供大家參考。

    下面逐一說一下權限指令各個類型的使用方法:

    //角色權限
    <component v-permission:role='leader'></component>
    //判斷當前登錄人
    <component v-permission:currentUser='orderInfo.createUser'></component>
    //判斷業務狀態
    <component v-permission:bussinessStatus='{status:orderStatus.RUNNING,value:orderInfo.status}'></component>
    //角色是leader或者是當前訂單的創建者,有權限
    <component v-permission:some="{role:'leader',currentUser:'orderInfo.createUser'}"></component>
    //角色是leader并且是當前訂單的創建者,有權限
    <component v-permission:every="{role:'leader',currentUser:'orderInfo.createUser'}"></component>

    輸入限制

    v-input 輸入框限制,限制數字、保留n位小數點等。

    export default {
        inserted: function (el, binding, vnode) {
            el.addEventListener('input', function (e) {
                if (binding.arg == 'toFixed') {
                    //限制輸入n位小數點
                    toFiexd(e.target, vnode, binding.value)
                } else {
                    //限制數字輸入
                    Integer(e.target, vnode)
                }
            })
        },
    }
    function toFiexd(target, vnode, v) {
        console.log(v);
        let ln = 2;
        if (v) {
            ln = v;
        }
        var regStrs = [
            ['^0(\\d+)$', '$1'], //禁止錄入整數部分兩位以上,但首位為0
            ['[^\\d\\.]+$', ''], //禁止錄入任何非數字和點
            ['\\.(\\d?)\\.+', '.$1'], //禁止錄入兩個以上的點
            ['^(\\d+\\.\\d{' + ln + '}).+', '$1'] //禁止錄入小數點后兩位以上
        ];
        for (var i = 0; i < regStrs.length; i++) {
            var reg = new RegExp(regStrs[i][0]);
            target.value = target.value.replace(reg, regStrs[i][1]);
        }
        //對于封裝的像el-input組件,因為其需要通過input事件同步狀態
        if(vnode.componentInstance){
          vnode.componentInstance.$listeners.input(target.value)
        }
    }
    function Integer(target, vnode) {
        let valueStr = target.value
        if (valueStr.length == 1) {
            //第一個數字不為0
            valueStr = valueStr.replace(/[^0-9]/g, "");
        } else {
            //只能輸入正整數
            valueStr = valueStr.replace(/\D/g, "");
        }
        target.value = valueStr;
        if(vnode.componentInstance){
          vnode.componentInstance.$listeners.input(target.value)
        }
    }

    這里需要特別注意的是下面這行代碼

    vnode.componentInstance.$listeners.input(target.value)

    我們為什么需要添加這一句呢,我們明明已經為target.value做了賦值。

    實際上這一句代碼相當于指令作用組件內部的$emit('input',target.value),這是因為如果我們是在antd或者elementui中的輸入框組件上添加我們定義的v-input指令,直接為target.value賦值是不能生效的,修改的只是原生input控件value值,并沒有修改自定義組件的value,還需要通過觸發input事件去同步組件狀態,修改value值。(這里不了解為什么需要觸發input事件區同步狀態的同學了解一下v-model的語法糖原理即可理解,

    使用方法:

    <!-- 限制輸入兩位小數數字 -->
    <input v-input:toFixed="2"/>
    <!-- 限制輸入正整數 -->
    <el-input v-input:integer/>

    內容處理

    我們也可以通過自定義指令做對內容到處理,比如

    • 空值處理

    • 數字千分數逗號分割

    export default {
        bind:function(){
        },
        inserted:function(el,binding){
            dealContent(el,binding)
        },
        update:function(el,binding){
            dealContent(el,binding)
        },
        componentUpdated:function(){
        },
        unbind:function(){
        },
    }
    function dealContent(el,binding){
       const {arg}=binding;
       if(arg=='empty'){
           if(!el.textContent){//空值顯示
                el.textContent=binding.value||'暫無數據';
            }
       }else if(arg=='money'){//金額千分位逗號分割,如10000000顯示為100,000,00
            if (binding.value) {
                el.textContent = dealMoney(binding.value);
            }else {
                el.textContent = dealMoney(el.textContent);
            }
       }
    }

    千分位分割代碼:

    //金額處理
    export function dealMoney(money, places = 2) {
        const zero = `0.00`;
        if (isNaN(money) || money === '') return zero;
        if (money && money != null) {
            money = `${money}`;
            let left = money.split('.')[0]; // 小數點左邊部分
            let right = money.split('.')[1]; // 小數點右邊
            // 保留places位小數點,當長度沒有到places時,用0補足。
            right = right ? (right.length >= places ? '.' + right.substr(0, places) : '.' + right + '0'.repeat(places - right.length)) : ('.' + '0'.repeat(places));
            var temp = left.split('').reverse().join('').match(/(\d{1,3})/g); // 分割反向轉為字符串然后最多3個,最少1個,將匹配的值放進數組返回
            return (Number(money) < 0 ? '-' : '') + temp.join(',').split('').reverse().join('') + right; // 補齊正負號和貨幣符號,數組轉為字符串,通過逗號分隔,再分割(包含逗號也分割)反向轉為字符串變回原來的順序
        } else if (money === 0) {
            return zero;
        } else {
            return zero;
        }
    }

    使用方法:

    <span v-content:empty="'無'">{{message}}</span>
    <!-- 金額千分位逗號分割 -->
    <span v-content:money>100000</span>

    文件預覽

    v-preview方便的實現文件預覽功能

    • 預覽圖片;

    • 預覽文件;

    • 其他預覽類業務功能

    import {isOffic,isPdf,isImage} from '@/utils/base'
    import {previewWithOffice} from '@/utils/fileUtils.js'
    export default {
        inserted:function(el,binding){
            el.onclick=function(e){
                let params = binding.value
                if(isOffic(params.name)){
                    e.preventDefault()
                    e.stopPropagation()
                    previewWithOffice(params.url)//使用office在線預覽打開
                }else if(isPdf(params.name) || isImage(params.name)){
                    e.preventDefault()
                    e.stopPropagation()
                    if(params.url){//直接打開url
                        previewFile(params)
                    }
                }
            }
        },
        //指令所在組件的 VNode 及其子 VNode 全部更新后
        componentUpdated: function (el, binding) {
            el.onclick=function(e){
                let params = binding.value
                if(isOffic(params.name)){
                    //使用插件預覽Office文件
                    e.preventDefault()
                    e.stopPropagation()
                    previewWithOffice(params.url)
                }else if(isPdf(params.name) || isImage(params.name)){
                   //預覽圖片和pdf等能直接打開的文件
                    e.preventDefault()
                    e.stopPropagation()
                    previewFile(params)
                }
            }
        },
        unbind(el){
           el.onclick=null;
        }
    }
    //預覽圖片和pdf等能直接打開的文件
    function previewFile(params) {
        let a = document.createElement("a");
        a.download = params.name
        a.href = params.url;
        a.target = "_blank";
        a.click();
        a = null;
    }

    使用方法:

    <!-- 預覽圖片 -->
    <image :src='url' v-preview="{name:file.name,url:file.url}"></image>
    <!-- 預覽文件 -->
    <span v-preview="{name:file.name,url:file.url}">{{file.name}}</span>

    關于“怎么完全掌握Vue自定義指令”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    vue
    AI

    洛扎县| 泗洪县| 东台市| 韶山市| 永平县| 定西市| 合水县| 叙永县| 陆丰市| 泾川县| 阳山县| 黎城县| 阿拉善盟| 西藏| 凤翔县| 原阳县| 盐池县| 辰溪县| 徐水县| 惠州市| 永顺县| 海兴县| 鄄城县| 许昌县| 武鸣县| 黄平县| 灵石县| 南安市| 宁阳县| 于都县| 栖霞市| 河曲县| 绥棱县| 满城县| 安顺市| 乐昌市| 武宁县| 河西区| 洛浦县| 固始县| 通辽市|