likes
comments
collection
share

Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

作者站长头像
站长
· 阅读数 110

指令本质

在 vue 中我们经常使用到 v-ifv-showv-forv-bindv-on 等内置指令,有的时候就很疑惑它们内部是如何运行的?

vue 中以 v- 开头的都是语法糖,会被编译为 js 可以理解的代码。

  • v-bind:会被编译到 _sfc_render 函数的 $setup 属性上,由于使用了响应式数据自动触发依赖收集。

示例:

<template>
  <div id="app">
    <div v-bind="countRef">绑定响应式数据</div>
  </div>
</template>

被编译为

Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

  • v-if:经过编译后是一个三目运算符,在条件成立时采用 createElementBlock 函数创建对应的 vnode,在条件不成立时采用 createCommonVnode 函数创建注释节点。

示例:

<template>
  <div id="app">
    <div v-if="countRef">条件为真时显示</div>
    <div v-else-if="countRef === 1">数量为1时显示</div>
    <div v-else>条件为假时显示</div>
  </div>
</template>

被编译为:

Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

  • v-on:事件会直接加在元素上面,如果使用了修饰符则使用 withModifiers 函数处理修饰符后再根据情况运行事件处理函数。

示例:

<div id="app">
  <div v-on:click.stop="handleClick">点击我</div>
</div>

被编译为:

Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

注意:事件会被编译后会当作属性的一员。 Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

withModifiers 函数内部代码:

const systemModifiers = ["ctrl", "shift", "alt", "meta"];
const modifierGuards = {
  stop: (e) => e.stopPropagation(),
  prevent: (e) => e.preventDefault(),
  self: (e) => e.target !== e.currentTarget,
  ctrl: (e) => !e.ctrlKey,
  shift: (e) => !e.shiftKey,
  alt: (e) => !e.altKey,
  meta: (e) => !e.metaKey,
  left: (e) => "button" in e && e.button !== 0,
  middle: (e) => "button" in e && e.button !== 1,
  right: (e) => "button" in e && e.button !== 2,
  exact: (e, modifiers) => systemModifiers.some((m) => e[`${m}Key`] && !modifiers.includes(m)) // 它使得事件监听器只响应完全匹配的键盘事件
};
const withModifiers = (fn, modifiers) => {
  const cache = fn._withMods || (fn._withMods = {});
  const cacheKey = modifiers.join("."); // 将数组每一项通过.连接起来转为字符串。
  return cache[cacheKey] || (cache[cacheKey] = (event, ...args) => {
    for (let i = 0; i < modifiers.length; i++) {
      const guard = modifierGuards[modifiers[i]]; // 找到修饰符对应的处理函数。
      if (guard && guard(event, modifiers)) return; // 执行修饰符处理函数根据返回值决定是否执行事件处理函数。
    }
    return fn(event, ...args);
  });
};
  • v-for:经过编译后是一个 renderList 函数内部采用 createElementBlock 函数创建对应的 vnode

示例:

<div id="app">
  <div v-for="item in dataList" :key="item.key">
    <p>{{item.value}}</p>
  </div>
</div>

被编译为:

Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

  • v-show:实际上就是内置的自定义指令,在 beforMount 时获取元素的 display 属性并保存起来,在 update 时根据指令值判断是否将 display 设为 none

示例:

<div id="app">
  <div v-show="isShow">控制css样式来进行显隐</div>
</div>

被编译为:

Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

VShow 内部代码:

const vShow = {
  beforeMount(el, { value }, { transition }) {
    el[vShowOriginalDisplay] = el.style.display === "none" ? "" : el.style.display;
    if (transition && value) { // 这是和 transition 组件相关的代码。
      transition.beforeEnter(el);
    } else {
      setDisplay(el, value); // 这个函数根据 value 是否是虚值去设置 display 属性是否为 none。
    }
  },
  mounted(el, { value }, { transition }) {
    if (transition && value) {
      transition.enter(el);
    }
  },
  updated(el, { value, oldValue }, { transition }) {
    if (!value === !oldValue) return;
    if (transition) {
      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);
  }
};

{
  vShow.name = "show";
}

function setDisplay(el, value) {
  el.style.display = value ? el[vShowOriginalDisplay] : "none";
  el[vShowHidden] = !value;
}

自定义指令是如何运行的

通常我们写自定义指令都全局指令,例:

const app = createApp(App);
app.directive('test',{
    created(el,binding){}, // 在指令第一次绑定到元素时调用,此时可以访问到指令绑定的值,但元素尚未渲染到 DOM 中。
    beforeMount(el,binding){}, // 在绑定元素插入 DOM 前被调用。
    mounted(el,binding){}, // 在绑定元素被插入 DOM 后调用。
    beforeUpdate(el,binding){}, // 在绑定元素的组件更新之前被调用。
    updated(el,binding){}, // 在绑定元素的组件更新之后被调用。
    beforeUnmount(el,binding){}, // 在指令与元素解绑之前被调用。
    unmounted(el,binding){} // 在指令与元素解绑后被调用。
});
app.mount('#app');

在模板中使用 v-test 时,会使用 withDirectives 函数将配置项添加到 vnode.dirs 数组中,在 vnode 渲染的各个阶段取出对应的配置项去执行。

function withDirectives(vnode, directives) {
    // ...
    for (let i = 0; i < directives.length; i++) {
    let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i];
    // 下面以 v-on:click.once.stop="say('hello')" 为例子。
    vnode.dirs.push({
        dir, // dir表示指令的配置项,例:{created,beforeMount ...}
        value, // 指令值
        oldValue: void 0, 
        arg, // 表示参数,只能是字符串,例子中 click 就是字符串类型的参数。 
        modifiers // 表示修饰符,例子中.once.stop被编译为修饰符,{once: true,stop: true}。
    });
    }
}

插槽原理

插槽传递

在向组件传递插槽的时候实际上传递的是一个对象,这个对象的 key 是插槽名,value 是一个函数会返回在插槽中定义的虚拟节点。

<Comp>
    <p>我是默认插槽</p>
    <template #name>
        <p>我是具名插槽</p>
    </template>
    <template #dynamic="{msg}">
        <p>我是作用域插槽 {{msg}}</p>
    </template>
</Comp>

<!-- 这里传递的插槽会被转为对象 -->
<script>
    {
        default: function(...args){},
        name: function(...args){},
        dynamic: function(...args){},
    }
</script>

Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

插槽使用

在使用插槽的时候本质上就是在调用函数,将传递过来的对象里面的函数逐个调用得到的 vnode 放入 createElemntVnode 函数即可渲染出页面。

示例:

<template>
  <div id="app">
    <Test>
      <p>我是默认插槽</p>
      <template #name>
        <p>我是具名插槽</p>
      </template>
      <template #dynamic="{msg}">
        <p>我是作用域插槽 {{msg}}</p>
      </template>
    </Test>
  </div>
</template>

被编译为:

Vue内置指令指令本质 在 vue 中我们经常使用到 v-if、v-show、v-for、v-bind、v-on 等内置

简化:

export default {
    setup(_,{slots}){
        const default = slots.default();
        const name = slots.name();
        const dynamic = slots.dynamic({msg: '这是消息'});
        
        return () => {
            return createEleemntVnode('div','',[...default,...name,...dynamic]);
        }
    }
}
转载自:https://juejin.cn/post/7425465315972317184
评论
请登录