美文网首页vue收藏
Vue常用的内置指令的底层细节分析

Vue常用的内置指令的底层细节分析

作者: chonglingliu | 来源:发表于2022-01-25 20:02 被阅读0次

    上一篇文章我们知道了指令的实现原理,接下来我们来研究下Vue提供的一些默认指令的实现原理。

    v-text

    使用案例
    <div v-text="'value'"
    
    实现逻辑
    • 先来看下render函数
    const _hoisted_1 = ["textContent"]
    
    function render(_ctx, _cache) {
      with (_ctx) {
        const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
    
        return (_openBlock(), _createElementBlock("div", {
          textContent: _toDisplayString('value')
        }, null, 8 /* PROPS */, _hoisted_1))
      }
    }
    })
    

    在创建VNode的时候传递了一个textContentpro

    export function patchDOMProp(
      el: any,
      key: string,
      value: any,
      prevChildren: any,
      parentComponent: any,
      parentSuspense: any,
      unmountChildren: any
    ) {
      if (key === 'innerHTML' || key === 'textContent') {
        // 如果`textContent`直接更新元素的textContent
        el[key] = value == null ? '' : value
        return
      }
    }
    

    这个pro直接被用来作为元素的textContent

    总结

    v-text设置元素的textContent

    v-html

    我们通过上面的代码,估计你看到innerHTML应该就理解了v-html的实现逻辑。v-html是渲染函数生成VNode的时候传了一个innerHTMLpro, 这个pro直接被用来作为元素的 innerHTML

    • 验证下确实如此
    const _hoisted_1 = ["innerHTML"]
    
    function render(_ctx, _cache) {
      with (_ctx) {
        const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
    
        return (_openBlock(), _createElementBlock("div", { innerHTML: 'value' }, null, 8 /* PROPS */, _hoisted_1))
      }
    }
    
    总结

    v-html设置元素的innerHTML

    v-show

    使用案例
    <div v-show="true">div元素</div>
    
    实现逻辑
    • 先来看下render函数
    function render(_ctx, _cache) {
      with (_ctx) {
        const { vShow: _vShow, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
    
        return _withDirectives((_openBlock(), _createElementBlock("div", null, "div元素", 512 /* NEED_PATCH */)), [
          [_vShow, false]
        ])
      }
    }
    

    v-show实现逻辑:绑定了vShow指令在元素上

    • 我们来看下v-show这个内部指令
    interface VShowElement extends HTMLElement {
      // _vod = vue original display
      _vod: string
    }
    
    export const vShow: ObjectDirective<VShowElement> = {
      beforeMount(el, { value }, { transition }) {
        el._vod = el.style.display === 'none' ? '' : el.style.display
        setDisplay(el, value)
      },
      updated(el, { value, oldValue }, { transition }) {
        setDisplay(el, value)
      },
      beforeUnmount(el, { value }) {
        setDisplay(el, value)
      }
    
    }
    
    function setDisplay(el: VShowElement, value: unknown): void {
      el.style.display = value ? el._vod : 'none'
    }
    

    v-show在内部就是切换元素的style.display。如果传入的值为false就将style.display设置为none不显示,如果传入的值为true,则是元素原本设置style.display值。

    总结

    v-show控制元素的style.display来切换显示和隐藏。

    v-if && v-else-if && v-else

    使用案例
    <!-- 数据 -->
    let condition = ref(1);
    
    <!-- 模板 -->
    <div v-if="condition == 1">状态1</div>
    <div v-else-if="condition == 2">状态2</div>
    <div v-else>其他状态</div>
    
    实现逻辑
    • 来看下render函数
    const _hoisted_1 = { key: 0 }
    const _hoisted_2 = { key: 1 }
    const _hoisted_3 = { key: 2 }
    
    function render(_ctx, _cache) {
      with (_ctx) {
        const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
    
        return (condition == 1)
          ? (_openBlock(), _createElementBlock("div", _hoisted_1, "状态1"))
          : (condition == 2)
            ? (_openBlock(), _createElementBlock("div", _hoisted_2, "状态2"))
            : (_openBlock(), _createElementBlock("div", _hoisted_3, "其他状态"))
      }
    }
    

    v-if && v-else-if && v-else实现逻辑:直接进行表达式的判断,然后不同的表达式渲染不同的DOM元素。

    重要知识点

    问题:v-if && v-else-if && v-else切换时会进行元素复用吗?

    答案:不会。因为不同的元素赋予了不同的key, const _hoisted_1 = { key: 0 } const _hoisted_2 = { key: 1 } const _hoisted_3 = { key: 2 } 这样切换条件,会直接卸载旧的元素节点,挂载新的元素节点,不会进行复用。

    v-for

    使用案例
    let items = [{
      id: 1,
      name: "张三"
    }, 
    {   id:2, 
      name: "李四"
    }];
    
    <div v-for="(item, index) in items" :key="item.id">{{ item.name }}</div>
    
    实现逻辑
    • 来看下render函数
    function render(_ctx, _cache) {
      with (_ctx) {
        const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString } = _Vue
    
        return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, index) => {
          return (_openBlock(), _createElementBlock("div", { key: item.id }, _toDisplayString(item.name), 1 /* TEXT */))
        }), 128 /* KEYED_FRAGMENT */))
      }
    }
    

    渲染结果是Fragment中包含了每个Item对应的Element

    重要知识点

    问题:v-for 的遍历的数据只能是数组吗?

    答案:不是

    1. 如果是数组,则遍历的是数组的每个元素;
    2. 如果是字符串,则遍历的是字符串的每个字符;
    3. 如果是数字,则遍历的是从 0 到 数据对应的那个值;
    4. 如果是实现了可迭代协议的数据,则是迭代遍历到的所有值;
    5. 如果是对象,则遍历的所有的key锁对应的值;

    问题:v-ifv-for同时使用,会有优先使用哪个指令?

    <div v-if="items.length > 0" v-for="(item, index) in items" :key="item.id">{{ item.name }}</div>
    
    function render(_ctx, _cache) {
      with (_ctx) {
        const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createCommentVNode: _createCommentVNode } = _Vue
    
        return (items.length > 0)
          ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(items, (item, index) => {
              return (_openBlock(), _createElementBlock("div", { key: item.id }, _toDisplayString(item.name), 1 /* TEXT */))
            }), 128 /* KEYED_FRAGMENT */))
          : _createCommentVNode("v-if", true)
      }
    }
    

    答案:优先判断v-if指令,如果条件成立,才会进行v-for遍历生成数组元素节点。

    其他

    还有一些其他常用的指令,例如v-model进行双向绑定,v-on进行事件绑定,v-slot进行插槽的设置。这几个指令由于相对复杂,我们将每个使用一个章节来介绍。本章节就到此为止。

    相关文章

      网友评论

        本文标题:Vue常用的内置指令的底层细节分析

        本文链接:https://www.haomeiwen.com/subject/vdxghrtx.html