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

上一篇文章我们知道了指令的实现原理,接下来我们来研究下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进行插槽的设置。这几个指令由于相对复杂,我们将每个使用一个章节来介绍。本章节就到此为止。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 常用内置指令 v-bind: 主要用于动态的绑定DOM元素属性,即属性值来自 Vue 中的 data 属性提...
    Negen阅读 752评论 0 7
  • vue指令 v-if v-if指令可以完全根据表达式的值在DOM中生成或者移除一个元素。v-if是惰性的,如果初始...
    G_石头阅读 453评论 0 0
  • 常用内置指令 v-text : 更新元素的 textContent v-html : 更新元素的 innerHTM...
    无药可救的渣渣阅读 65评论 0 0
  • 注意: 1、所谓指令,其实就是元素的属性 2、所有的指令,前提是在js中声明了是Vue对象 3、参数 1、静态参数...
    张浩琦阅读 386评论 0 0
  • 指令( Directives )是 Vue 模板中最常用的一项功能,它带有前缀 v-,能帮我们快速完成DOM操作,...
    苦瓜_6阅读 316评论 0 0