前言
我们每天写vue3代码的时候都会使用到setup语法糖,那你知道为什么setup语法糖中的顶层绑定可以在template中直接使用的呢?setup语法糖是如何编译成setup函数的呢?本文将围绕这些问题带你揭开setup语法糖的神秘面纱。注:本文中使用的vue版本为3.4.19
。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
看个demo
看个简单的demo,代码如下:
<template>
<h1>{{ msg }}h1>
<h2>{{ format(msg) }}h2>
<h3>{{ title }}h3>
<Child />
template>
<script lang="ts" setup>
import { ref } from "vue";
import Child from "./child.vue";
import { format } from "./util.js";
const msg = ref("Hello World!");
let title;
if (msg.value) {
const innerContent = "xxx";
console.log(innerContent);
title = "111";
} else {
title = "222";
}
script>
在上面的demo中定义了四个顶层绑定:Child
子组件、从util.js
文件中导入的format
方法、使用ref定义的msg
只读常量、使用let定义的title
变量。并且在template中直接使用了这四个顶层绑定。
由于innerContent
是在if语句里面的变量,不是中的顶层绑定,所以在template中是不能使用
innerContent
的。
但是你有没有想过为什么中的顶层绑定就能在template中使用,而像
innerContent
这种非顶层绑定就不能在template中使用呢?
我们先来看看上面的代码编译后的样子,在之前的文章中已经讲过很多次如何在浏览器中查看编译后的vue文件,这篇文章就不赘述了。编译后的代码如下:
import { defineComponent as _defineComponent } from "/node_modules/.vite/deps/vue.js?v=23bfe016";
import { ref } from "/node_modules/.vite/deps/vue.js?v=23bfe016";
import Child from "/src/components/setupDemo2/child.vue";
import { format } from "/src/components/setupDemo2/util.js";
const _sfc_main = _defineComponent({
__name: "index",
setup(__props, { expose: __expose }) {
__expose();
const msg = ref("Hello World!");
let title;
if (msg.value) {
const innerContent = "xxx";
console.log(innerContent);
title = "111";
} else {
title = "222";
}
const __returned__ = {
msg,
get title() {
return title;
},
set title(v) {
title = v;
},
Child,
get format() {
return format;
},
};
return __returned__;
},
});
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
// ...省略
}
_sfc_main.render = _sfc_render;
export default _sfc_main;
从上面的代码中可以看到编译后已经没有了,取而代之的是一个setup函数,这也就证明了为什么说setup是一个编译时语法糖。
setup函数的参数有两个,第一个参数为组件的 props
。第二个参数为Setup 上下文对象,上下文对象暴露了其他一些在 setup
中可能会用到的值,比如:expose
等。
再来看看setup函数中的内容,其实和我们的源代码差不多,只是多了一个return。使用return会将组件中的那四个顶层绑定暴露出去,所以在template中就可以直接使用中的顶层绑定。
值的一提的是在return对象中title
变量和format
函数有点特别。title
、format
这两个都是属于访问器属性,其他两个msg
、Child
属于常见的数据属性。
title
是一个访问器属性,同时拥有get
和 set
,读取title
变量时会走进get
中,当给title
变量赋值时会走进set
中。
format
也是一个访问器属性,他只拥有get
,调用format
函数时会走进get
中。由于他没有set
,所以不能给format
函数重新赋值。其实这个也很容易理解,因为format
函数是从util.js
文件中import导入的,当然不能给他重新赋值。
至于在template中是怎么拿到setup
函数返回的对象可以看我的另外一篇文章: Vue 3 的 setup语法糖到底是什么东西?
看到这里有的小伙伴会有疑问了,不是还有一句import { ref } from "vue"
也是顶层绑定,为什么里面的ref
没有在setup函数中使用return暴露出去呢?还有在return对象中是如何将title
、format
识别为访问器属性呢?
在接下来的文章中我会逐一解答这些问题。
compileScript
函数
在之前的 通过debug搞清楚.vue文件怎么变成.js文件文章中已经讲过了vue的script模块中的内容是由@vue/compiler-sfc
包中的compileScript
函数处理的,当然你没看过那篇文章也不会影响这篇文章的阅读。
首先我们需要启动一个debug终端。这里以vscode
举例,打开终端然后点击终端中的+
号旁边的下拉箭头,在下拉中点击Javascript Debug Terminal
就可以启动一个debug
终端。
然后在node_modules
中找到vue/compiler-sfc
包的compileScript
函数打上断点,compileScript
函数位置在/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js
。接下来我们先看看简化后的compileScript
函数源码。
简化后的compileScript
函数
在debug
终端上面执行yarn dev
后在浏览器中打开对应的页面,比如:http://localhost:5173/ 。此时断点就会走到compileScript
函数中,在我们这个场景中简化后的compileScript
函数代码如下:
function compileScript(sfc, options) {
// ---- 第一部分 ----
// 根据