最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue3疑问系列(3) — v-show指令是如何工作的?

    正文概述 掘金(徐志伟酱)   2021-02-26   607

    前言

    根据上2篇文章的讲解,指令的工作原理明白了, 那接下来就是实战篇.
    本文的实战不是我们工作的业务场景使用指令实战,而是Vue3中内置的vShow指令的解读.
    看看尤大是如何编写vShow指令的.
    我们知道指令的作用就是在元素安装、更新、卸载时分别调用不同的钩子,我们开发者在不同的钩子中执行不同的业务逻辑.
    vShow指令所要做的功能就是显示和隐藏dom元素(组件也能显示和隐藏)

    v-show的使用

    单测原地址

    const withVShow = (node: VNode, exp: any) =>
      withDirectives(node, [[vShow, exp]])
    
    let root: any
    
    beforeEach(() => {
      root = document.createElement('div')
    })
    
    it('should update show value changed', async () => {
        const component = defineComponent({
          data() {
            return { value: true }
          },
          render() {
            return [withVShow(h('div'), this.value)]
          }
        })
        render(h(component), root)
    
        const $div = root.querySelector('div')
        const data = root._vnode.component.data
    
        expect($div.style.display).toEqual('')
    
        data.value = false
        await nextTick()
        expect($div.style.display).toEqual('none')
    
        data.value = {}
        await nextTick()
        expect($div.style.display).toEqual('')
    
        data.value = 0
        await nextTick()
        expect($div.style.display).toEqual('none')
    
        data.value = []
        await nextTick()
        expect($div.style.display).toEqual('')
    
        data.value = null
        await nextTick()
        expect($div.style.display).toEqual('none')
    
        data.value = '0'
        await nextTick()
        expect($div.style.display).toEqual('')
    
        data.value = undefined
        await nextTick()
        expect($div.style.display).toEqual('none')
    
        data.value = 1
        await nextTick()
        expect($div.style.display).toEqual('')
    })
    
    • 这个单侧比较简单,就是改变component组件实例的value值来显示隐藏div.
    • 考虑到部分同学看不懂render函数,下面改成了模板的形式
    const component = defineComponent({
        data() {
            return { value: true }
        },
        template: `
            <div v-show="value"></div>
        `
    })
    
    • 该单侧就是告诉我们,只要value是真值就显示,value是假值就不显示.

    vShow内部实现

    自己先写个vShow呢

    1. 在看vue3中的vShow指令的实现之前,我们能不能自己写一个vShow呢,毕竟我们都懂指令的工作原理了,那还不好写吗.
    2. 需求: 编写一个指令,绑定在dom和组件上,当绑定的值为真值时就显示,假值的时候就隐藏
    3. 代码如下
    const vShow = (() => {
    
        const setDisplay = (el, value) => {
            el.style.display = value
                ? (el._originalDisplayValue === 'none' ? '' : el._originalDisplayValue) 
                : 'none'
        }
    
        return {
            beforeMount(el, binding, vnode, prevVNode) {
                el._originalDisplayValue = el.style.display
                setDisplay(el, binding.value)
            },
            updated(el, binding) {
                setDisplay(el, binding.value)
            },
            beforeUnmount(el, binding) {
                setDisplay(el, binding.value)
            }
        }
        
    })()
    

    4.为了验证结果,可以狠狠的点这个链接 jsfiddle.net/kpdqtovm/9/

    vue3内部vShow实现

    runtime-dom模块和runtime-core模块

    1. runtime-core: runtime-core 比 runtime-dom 偏底层,很多核心的api都是来自此模块,元素/组件的安装包括diff算法,包括上次讲解的指令内部实现,和内置组件transition keepalive 的底层实现.

    2. runtime-dom: 就是调用runtime-core的api来实现一些好用的指令(v-show|v-model),组件(transition|transition-group),还有元素属性的添加和事件添加及解绑方法,都写在这个模块下

    3. 分这么细的好处我觉得是为了跨平台,不仅限于在浏览器端运行(因为创建render函数调用baseCreateRenderer时,可以传入insert, remove, patchProp, forcePatchProp, createElement, createText, createComment, setText, setElementText, parentNode, nextSibling, setScopeId, cloneNode, insertStaticContent这些自定义的方法),runtime-test模块就是最好的证明,大家感兴趣可以看看.这样 runtime-test可以调用runtime-core, runtime-dom也可以调用 runtime-core, 以后张三李四想扩展其他模块也很简单.

    指令的注册

    1. vue3内置的指令注册是不存在的. what? 不和vue2一样了? 那我不也使用v-show没出问题吗? 你在忽悠我?

    2. 首先确实是没有帮我们全局注册,那为啥我们开发者还能像vue2正常使用呢?

    3. vShow的源码在runtime-dom/src/directives/vShow.ts文件文中,runtime-dom/src/index.ts文件中引入了vShow.ts并且导出了vShow指令对象

    4. vue模块中导出了所有runtime-dom导出方法,也就是我们的大Vue身上有vShow指令对象

    5. 我们使用模板时,编译模块会把我们的模板编译成render函数,render函数中会从Vue身上拿到vShow,然后通过_withDirectives方法来调用(这就回到我们上2篇文章中单测得例子,单测真的很重要,不要一味着使用模板),所以能正常工作

        <template>
            <div v-show="value">hi</div>
        </template>
    
        // 编译成
    
        (function anonymous() {
            const _Vue = Vue
    
            return function render(_ctx, _cache) {
                with (_ctx) {
                    const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
    
                    return _withDirectives((_openBlock(), _createBlock("div", null, "hi", 512 /* NEED_PATCH */)), [
                        [_vShow, value]
                    ])
                }
            }
        })
    
    1. 其次我们用户注册指令时,如何指令名和内置的指令名一样,也会报错误提示.

    vShow实现

    1. 既然模板上标签使用指令最终会变成 Vue.withDirectives(vnode, [[vShow|vModel|自定义directive, value, argument, modifiers]])

    2. 那我们看看vShow到底怎么实现的?

    3. vShow源码 runtime-dom/src/directives/vShow.ts

    
    export const vShow: ObjectDirective<VShowElement> = {
      beforeMount(el, { value }, { transition }) {
        el._vod = el.style.display === 'none' ? '' : el.style.display
        if (transition && value) {
          transition.beforeEnter(el)
        } else {
          setDisplay(el, value)
        }
      },
      mounted(el, { value }, { transition }) {
        if (transition && value) {
          transition.enter(el)
        }
      },
      updated(el, { value, oldValue }, { transition }) {
        if (transition && value !== oldValue) {
          if (value) {
            transition.beforeEnter(el)
            setDisplay(el, true)
            transition.enter(el)
          } else {
            transition.leave(el, () => {
              setDisplay(el, false)
            })
          }
        } else {
          setDisplay(el, value)
        }
      },
      beforeUnmount(el, { value }) {
        setDisplay(el, value)
      }
    }
    
    function setDisplay(el: VShowElement, value: unknown): void {
      el.style.display = value ? el._vod : 'none'
    }
    
    • 可以看的出, 尤大的实现还考虑了transition动画组件,抛开transition组件的实现,其实和我们上面写的vShow实现差不多,那就看看呗

    • 首先所有钩子,接收4个参数(el:根据vnode创建好的dom元素, binding:参数、修饰符、新的值、旧的值、组件实例, vnode: 当前vnode, prevVNode: 上一次的vnode)

    • beforeMount: 当元素创建好且没有插入到父元素上执行beforeMount钩子,给el添加_vod属性,值为el的display的原始值, 如果有transiton组件且value为真值,则执行transition.beforeEnter(el)否则根据 value值设置el的dispaly.

    • mounted: 当元素安装完后调用beforeMount钩子,如果有transiton组件且value为真值,则执行transition.enter(el)

    • updated: 当元素更新后调用updated钩子,如果有transiton组件且新旧值不同则处理transition进入和离开的逻辑,否则根据value给el设置display值

    • beforeUnmount: 当元素卸载前执行beforeUnmount钩子,根据value给el设置display值

    1. transition组件的实现我还没看,后期会单独写两篇文章分别介绍runtime-core下的BaseTransition和runtime-dom下的Transition,这里不做介绍.

    2. 其实明白了指令的内部工作原理,看vShow的实现还是很简单的.

    总结

    vShow的实现就是在不同时机的钩子中,根据绑定的值,来显示和隐藏el元素

    下篇: Vue3疑问系列(4) — v-model(vModelText)指令是如何工作的?


    起源地下载网 » Vue3疑问系列(3) — v-show指令是如何工作的?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元