[vue3 编译原理揭秘] vue3 的宏到底是什么东西? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ouyangcoder
V2EX    推广

[vue3 编译原理揭秘] vue3 的宏到底是什么东西?

  •  
  •   ouyangcoder 2024-05-05 22:43:23 +08:00 1713 次点击
    这是一个创建于 526 天前的主题,其中信息可能已经有所发展或是发生改变。

    前言

    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顶层的宏,其他地方的宏会原封不动的输出回来。在运行时由于我们没有在任何地方定义这些宏,当代码执行到宏的时候当然就会报错。

    如果想要在vue中使用更多的宏,可以使用 vue macros。这个库是用于在 vue 中探索更多的宏和语法糖,作者是 vue 的团队成员 三智子

    关注(图 1 )公众号: [前端欧阳] ,解锁我更多 vue 原理文章。

    加我(图 2 )微信回复「资料」,免费领取欧阳研究 vue 源码过程中收集的源码资料,欧阳写文章有时也会参考这些资料。同时让你的朋友圈多一位对 vue 有深入理解的人。

    公众号微信

    2 条回复
    wusheng0
        1
    wusheng0  
       2024-05-06 00:33:07 +08:00 via Android
    TaiShang
        2
    TaiShang  
       2024-05-06 09:33:12 +08:00
    支持
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     962 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 19:47 PVG 03:47 LAX 12:47 JFK 15:47
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86