vue3的宏到底是什么东西?

摘要

vue3开始vue引入了宏,比如definePropsdefineEmits等。我们每天写vue代码时都会使用到这些宏,但是你有没有思考过vue中的宏到底是什么?为什么这些宏不需要手动从vueimport?为什么只能在setup顶层中使用这些宏?

vue 文件如何渲染到浏览器上

要回答上面的问题,我们先来了解一下从一个vue文件到渲染到浏览器这一过程经历了什么?

我们的vue代码一般都是写在后缀名为vue的文件上,显然浏览器是不认识vue文件的,浏览器只认识html、css、jss等文件。所以第一步就是通过webpack或者vite将一个vue文件编译为一个包含render函数的js文件。然后执行render函数生成虚拟DOM,再调用浏览器的DOM API根据虚拟DOM生成真实DOM挂载到浏览器上。

vue3的宏是什么?

vue官方的解释:

宏是一种特殊的代码,由编译器处理并转换为其他东西。它们实际上是一种更巧妙的字符串替换形式。

宏是在哪个阶段运行?

通过前面我们知道了vue 文件渲染到浏览器上主要经历了两个阶段。

第一阶段是编译时,也就是从一个vue文件经过webpack或者vite编译变成包含render函数的js文件。此时的运行环境是nodejs环境,所以这个阶段可以调用nodejs相关的api,但是没有在浏览器环境内执行,所以不能调用浏览器的API

第二阶段是运行时,此时浏览器会执行js文件中的render函数,然后依次生成虚拟DOM和真实DOM。此时的运行环境是浏览器环境内,所以可以调用浏览器的API,但是在这一阶段中是不能调用nodejs相关的api

而宏就是作用于编译时,也就是从vue文件编译为js文件这一过程。

举个defineProps的例子:在编译时defineProps宏就会被转换为定义props相关的代码,当在浏览器运行时自然也就没有了defineProps宏相关的代码了。所以才说宏是在编译时执行的代码,而不是运行时执行的代码。

一个defineProps宏的例子

我们来看一个实际的例子:

<template>
  <div>content is {{ content }}</div>
  <div>title is {{ title }}</div>
</template>

<script setup lang="ts">
import {ref} from "vue"
const props = defineProps({
  content: String,
});
const title = ref("title")
</script>

在这个例子中我们使用defineProps宏定义了一个类型为String,属性名为contentprops,并且在template中渲染content的内容。

我们接下来再看看编译成js文件后的代码,代码我已经进行过简化:

import { defineComponent as _defineComponent } from "vue";
import { ref } from "vue";

const __sfc__ = _defineComponent({
  props: {
    content: String,
  },
  setup(__props) {
    const props = __props;
    const title = ref("title");
    const __returned__ = { props, title };
    return __returned__;
  },
});

import {
  toDisplayString as _toDisplayString,
  createElementVNode as _createElementVNode,
  Fragment as _Fragment,
  openBlock as _openBlock,
  createElementBlock as _createElementBlock,
} from "vue";

function render(_ctx, _cache, $props, $setup) {
  return (
    _openBlock(),
    _createElementBlock(
      _Fragment,
      null,
      [
        _createElementVNode(
          "div",
          null,
          "content is " + _toDisplayString($props.content),
          1 /* TEXT */
        ),
        _createElementVNode(
          "div",
          null,
          "title is " + _toDisplayString($setup.title),
          1 /* TEXT */
        ),
      ],
      64 /* STABLE_FRAGMENT */
    )
  );
}
__sfc__.render = render;
export default __sfc__;

我们可以看到编译后的js文件主要由两部分组成,第一部分为执行defineComponent函数生成一个 __sfc__ 对象,第二部分为一个render函数。render函数不是我们这篇文章要讲的,我们主要来看看这个__sfc__对象。

看到defineComponent是不是觉得很眼熟,没错这个就是vue提供的API中的 definecomponent函数。这个函数在运行时没有任何操作,仅用于提供类型推导。这个函数接收的第一个参数就是组件选项对象,返回值就是该组件本身。所以这个__sfc__对象就是我们的vue文件中的script代码经过编译后生成的对象,后面再通过__sfc__.render = renderrender函数赋值到组件对象的render方法上面。

我们这里的组件选项对象经过编译后只有两个了,分别是props属性和setup方法。明显可以发现我们原本在setup里面使用的defineProps宏相关的代码不在了,并且多了一个props属性。没错这个props属性就是我们的defineProps宏生成的。

我们再来看一个不在setup顶层调用defineProps的例子:

<script setup lang="ts">
import {ref} from "vue"
const title = ref("title")

if (title.value) {
  const props = defineProps({
    content: String,
  });
}
</script>

运行这个例子会报错:defineProps is not defined

我们来看看编译后的js代码:

import { defineComponent as _defineComponent } from "vue";
import { ref } from "vue";

const __sfc__ = _defineComponent({
  setup(__props) {
    const title = ref("title");
    if (title.value) {
      const props = defineProps({
        content: String,
      });
    }
    const __returned__ = { title };
    return __returned__;
  },
});

明显可以看到由于我们没有在setup的顶层调用defineProps宏,在编译时就不会将defineProps宏替换为定义props相关的代码,而是原封不动的输出回来。在运行时执行到这行代码后,由于我们没有任何地方定义了defineProps函数,所以就会报错defineProps is not defined

总结

vue中的宏到底是什么?

vue3的宏是一种特殊的代码,在编译时会将这些特殊的代码转换为浏览器能够直接运行的指定代码,根据宏的功能不同,转换后的代码也不同。

为什么这些宏不需要手动从vueimport

因为在编译时已经将这些宏替换为指定的浏览器能够直接运行的代码,在运行时已经不存在这些宏相关的代码,自然不需要从vueimport

为什么只能在setup顶层中使用这些宏?

因为在编译时只会去处理setup顶层的宏,其他地方的宏会原封不动的输出回来。在运行时由于我们没有在任何地方定义这些宏,当代码执行到宏的时候当然就会报错。

### Vue3 中 `defineExpose` 的用法及其目的 在 Vue 3 Composition API 中引入了 `<script setup>` 语法糖,它简化了许多开发流程并提升了开发者体验。然而,在某些场景下,父组件可能需要访问子组件内部的状态或方法。为了实现这一需求,Vue 提供了一个名为 `defineExpose` 的工具。 #### 定义 `defineExpose` `defineExpose` 是用于显式声明哪些属性或方法可以从当前组件暴露给其父组件的一个工具[^1]。通过这种方式,可以控制哪些部分能够被外部访问,从而增强封装性和安全性。 以下是关于如何使用以及为什么要在脚本设置模式中使用它的具体说明: #### 使用示例 下面是一个简单的例子来展示 `defineExpose` 如何工作: ```vue <template> <button @click="increment">Increment</button> </template> <script setup> import { ref } from &#39;vue&#39; const count = ref(0) function increment() { count.value++ } // 显式地向外界暴露特定的内容 defineExpose({ count, increment }) </script> ``` 在这个例子中, 子组件中的 `count` 和 `increment()` 方法都被明确地标记为可对外部可见的部分。这意味着如果这个组件作为另一个更大应用的一部分存在的话,则可以通过模板引用的方式获取到这些公开的数据和行为。 #### 父级调用方式 假设我们有一个包含上述自定义按钮逻辑的小部件 (即上面创建的那个),那么可以在父级这样操作它们: ```vue <template> <child-component ref="myChildComponent"></child-component> <p>Count is {{ myChildComponent?.count }}</p> <button @click="handleClick">Call Increment From Parent</button> </template> <script setup> import ChildComponent from &#39;./path/to/ChildComponent.vue&#39; import { onMounted } from &#39;vue&#39; onMounted(() => { console.log(&#39;Exposed properties:&#39;, this.$refs.myChildComponent) }) function handleClick() { const childCompInstance = $refs.myChildComponent; if(childCompInstance && typeof childCompInstance.increment === &#39;function&#39;) { childCompInstance.increment(); } } </script> ``` 这里展示了当页面加载完成后会打印出由子组件所暴露出的对象;另外还提供了一种机制允许点击事件触发来自子组件的功能函数。 #### 设计意图 采用这种做法的主要原因是遵循最小权限原则——只让必要的东西变得可用而不是全部开放出去。这有助于保持良好的软件工程实践,减少不必要的耦合度,并使维护更加容易管理。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zqwang888

一毛不嫌少,一块不嫌多!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值