当前位置:首页 > 科技  > 软件

Vue 3 的 setup 语法糖到底是什么东西?

来源: 责编: 时间:2024-03-18 09:41:18 292观看
导读前言我们每天写vue3项目的时候都会使用setup语法糖,但是你有没有思考过下面几个问题。setup语法糖经过编译后是什么样子的?为什么在setup顶层定义的变量可以在template中可以直接使用?为什么import一个组件后就可以直接

前言

我们每天写vue3项目的时候都会使用setup语法糖,但是你有没有思考过下面几个问题。setup语法糖经过编译后是什么样子的?为什么在setup顶层定义的变量可以在template中可以直接使用?为什么import一个组件后就可以直接使用,无需使用components 选项来显式注册组件?07V28资讯网——每日最新资讯28at.com

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

要回答上面的问题,我们先来了解一下从一个vue文件到渲染到浏览器这一过程经历了什么?07V28资讯网——每日最新资讯28at.com

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

图片图片07V28资讯网——每日最新资讯28at.com

setup编译后的样子

在javascript标准中script标签是不支持setup属性的,浏览器根本就不认识setup属性。所以很明显setup是作用于编译时阶段,也就是从vue文件编译为js文件这一过程。07V28资讯网——每日最新资讯28at.com

我们来看一个简单的demo,这个是index.vue源代码:07V28资讯网——每日最新资讯28at.com

<template>  <h1>{{ title }}</h1>  <h1>{{ msg }}</h1>  <Child /></template><script lang="ts" setup>import { ref } from "vue";import Child from "./child.vue";const msg = ref("Hello World!");const title = "title";if (msg.value) {  const content = "content";  console.log(content);}</script>

这里我们定义了一个名为msg的ref响应式变量和非响应式的title变量,还有import了child.vue组件。07V28资讯网——每日最新资讯28at.com

这个是child.vue的源代码07V28资讯网——每日最新资讯28at.com

<template>  <div>i am child</div></template>

我们接下来看index.vue编译后的样子,代码我已经做过了简化:07V28资讯网——每日最新资讯28at.com

import { ref } from "vue";import Child from "./Child.vue";const title = "title";const __sfc__ = {  __name: "index",  setup() {    const msg = ref("Hello World!");    if (msg.value) {      const content = "content";      console.log(content);    }    const __returned__ = { title, msg, Child };    return __returned__;  },};import {  toDisplayString as _toDisplayString,  createElementVNode as _createElementVNode,  createVNode as _createVNode,  Fragment as _Fragment,  openBlock as _openBlock,  createElementBlock as _createElementBlock,} from "vue";function render(_ctx, _cache, $props, $setup, $data, $options) {  return (    _openBlock(),    _createElementBlock(      _Fragment,      null,      [        _createElementVNode("h1", null, _toDisplayString($setup.title)),        _createElementVNode(          "h1",          null,          _toDisplayString($setup.msg),          1 /* TEXT */        ),        _createVNode($setup["Child"]),      ],      64 /* STABLE_FRAGMENT */    )  );}__sfc__.render = render;export default __sfc__;

我们可以看到index.vue编译后的代码中已经没有了template标签和script标签,取而代之是render函数和__sfc__对象。并且使用__sfc__.render = render将render函数挂到__sfc__对象上,然后将__sfc__对象export default出去。07V28资讯网——每日最新资讯28at.com

看到这里你应该知道了其实一个vue组件就是一个普通的js对象,import一个vue组件,实际就是import这个js对象。这个js对象中包含render方法和setup方法。07V28资讯网——每日最新资讯28at.com

编译后的setup方法

我们先来看看这个setup方法,是不是觉得和我们源代码中的setup语法糖中的代码很相似?没错,这个setup方法内的代码就是由setup语法糖中的代码编译后来的。07V28资讯网——每日最新资讯28at.com

setup语法糖原始代码07V28资讯网——每日最新资讯28at.com

<script lang="ts" setup>import { ref } from "vue";import Child from "./child.vue";const msg = ref("Hello World!");const title = "title";if (msg.value) {  const content = "content";  console.log(content);}</script>

setup编译后的代码07V28资讯网——每日最新资讯28at.com

import { ref } from "vue";import Child from "./Child.vue";const title = "title";const __sfc__ = {  __name: "index",  setup() {    const msg = ref("Hello World!");    if (msg.value) {      const content = "content";      console.log(content);    }    const __returned__ = { title, msg, Child };    return __returned__;  },};

经过分析我们发现title变量由于不是响应式变量,所以编译后title变量被提到了js文件的全局变量上面去了。而msg变量是响应式变量,所以依然还是在setup方法中。我们再来看看setup的返回值,返回值是一个对象,对象中包含title、msg、Child属性,非setup顶层中定义的content变量就不在返回值对象中。07V28资讯网——每日最新资讯28at.com

看到这里,可以回答我们前面提的第一个问题。07V28资讯网——每日最新资讯28at.com

setup语法糖经过编译后是什么样子的?07V28资讯网——每日最新资讯28at.com

setup语法糖编译后会变成一个setup方法,编译后setup方法中的代码和script标签中的源代码很相似。方法会返回一个对象,对象由setup中定义的顶层变量和import导入的内容组成。07V28资讯网——每日最新资讯28at.com

由template编译后的render函数

我们先来看看原本template中的代码:07V28资讯网——每日最新资讯28at.com

<template>  <h1>{{ title }}</h1>  <h1>{{ msg }}</h1>  <Child /></template>

我们再来看看由template编译成的render函数:07V28资讯网——每日最新资讯28at.com

import {  toDisplayString as _toDisplayString,  createElementVNode as _createElementVNode,  createVNode as _createVNode,  Fragment as _Fragment,  openBlock as _openBlock,  createElementBlock as _createElementBlock,} from "vue";function render(_ctx, _cache, $props, $setup, $data, $options) {  return (    _openBlock(),    _createElementBlock(      _Fragment,      null,      [        _createElementVNode("h1", null, _toDisplayString($setup.title)),        _createElementVNode(          "h1",          null,          _toDisplayString($setup.msg),          1 /* TEXT */        ),        _createVNode($setup["Child"]),      ],      64 /* STABLE_FRAGMENT */    )  );}

我们这次主要看在render函数中如何访问setup中定义的顶层变量title、msg,createElementBlock和createElementVNode等创建虚拟DOM的函数不在这篇文章的讨论范围内。你只需要知道createElementVNode("h1", null, _toDisplayString($setup.title))为创建一个h1标签的虚拟DOM就行了。07V28资讯网——每日最新资讯28at.com

在render函数中我们发现读取title变量的值是通过$setup.title读取到的,读取msg变量的值是通过$setup.msg读取到的。这个$setup对象就是调用render函数时传入的第四个变量,我想你应该猜出来了,这个$setup对象就是我们前面的setup方法返回的对象。07V28资讯网——每日最新资讯28at.com

那么问题来了,在执行render函数的时候是如何将setup方法的返回值作为第四个变量传递给render函数的呢?我在下一节会一步一步的带你通过debug源码的方式去搞清楚这个问题,我们带着问题去debug源码其实非常简单。07V28资讯网——每日最新资讯28at.com

debug源码搞清楚是如何调用render函数

有的小伙伴看到这里需要看源码就觉得头大了,别着急,其实很简单,我会一步一步的带着你去debug源码。07V28资讯网——每日最新资讯28at.com

首先我们将Enable JavaScript source maps给取消勾选了,不然在debug源码的时候断点就会走到vue文件中,而不是走到编译会的js文件中。07V28资讯网——每日最新资讯28at.com

图片图片07V28资讯网——每日最新资讯28at.com

然后我们需要在设置里面的Ignore List看看node_modules文件夹是否被忽略。新版谷歌浏览器中会默认排除掉node_modules文件夹,所以我们需要将这个取消勾选。如果忽略了node_modules文件夹,那么debug的时候断点就不会走到node_modules中vue的源码中去了。07V28资讯网——每日最新资讯28at.com

图片图片07V28资讯网——每日最新资讯28at.com

接下来我们需要在浏览器中找到vue文件编译后的js代码,我们只需要在network面板中找到这个vue文件的http请求,然后在Response下右键选择Open in Sources panel,就会自动在sources面板自动打开对应编译后的js文件代码。07V28资讯网——每日最新资讯28at.com

图片图片07V28资讯网——每日最新资讯28at.com

找到编译后的js文件,我们想debug看看是如何调用render函数的,所以我们给render函数加一个断点。然后刷新页面,发现代码已经走到了断点的地方。我们再来看看右边的Call Stack调用栈,发现render函数是由一个vue源码中的renderComponentRoot函数调用的。07V28资讯网——每日最新资讯28at.com

图片图片07V28资讯网——每日最新资讯28at.com

点击Call Stack中的renderComponentRoot函数就可以跳转到renderComponentRoot函数的源码,我们发现renderComponentRoot函数中调用render函数的代码主要是下面这样的:07V28资讯网——每日最新资讯28at.com

function renderComponentRoot(instance) {  const {    props,    data,    setupState,    // 省略...  } = instance;  render2.call(    thisProxy,    proxyToUse,    renderCache,    props,    setupState,    data,    ctx  )}

这里我们可以看到前面的$setup实际就是由setupState赋值的,而setupState是当前vue实例上面的一个属性。那么setupState属性是如何被赋值到vue实例上面的呢?07V28资讯网——每日最新资讯28at.com

我们需要给setup函数加一个断点,然后刷新页面进入断点。通过分析Call Stack调用栈,我们发现setup函数是由vue中的一个setupStatefulComponent函数调用执行的。07V28资讯网——每日最新资讯28at.com

图片图片07V28资讯网——每日最新资讯28at.com

点击Call Stack调用栈中的setupStatefulComponent,进入到setupStatefulComponent的源码。我们看到setupStatefulComponent中的代码主要是这样的:07V28资讯网——每日最新资讯28at.com

function setupStatefulComponent(instance) {  const { setup } = Component;  // 省略  const setupResult = callWithErrorHandling(    setup,    instance  );  handleSetupResult(instance, setupResult);}

setup函数是Component上面的一个属性,我们将鼠标放到Component上面,看看这个Component是什么东西?07V28资讯网——每日最新资讯28at.com

图片图片07V28资讯网——每日最新资讯28at.com

看到这个Component对象中既有render方法也有setup方法是不是感觉很熟悉,没错这个Component对象实际就是我们的vue文件编译后的js对象。07V28资讯网——每日最新资讯28at.com

const __sfc__ = {  __name: "index",  setup() {    const msg = ref("Hello World!");    if (msg.value) {      const content = "content";      console.log(content);    }    const __returned__ = { title, msg, Child };    return __returned__;  },};__sfc__.render = render;

从Component对象中拿到setup函数,然后执行setup函数得到setupResult对象。然后再调用handleSetupResult(instance, setupResult);07V28资讯网——每日最新资讯28at.com

我们再来看看handleSetupResult函数是什么样的,下面是我简化后的代码:07V28资讯网——每日最新资讯28at.com

function handleSetupResult(instance, setupResult) {  if (isFunction(setupResult)) {    // 省略  } else if (isObject(setupResult)) {    instance.setupState = proxyRefs(setupResult);  }}

我们的setup的返回值是一个对象,所以这里会执行instance.setupState = proxyRefs(setupResult),将setup执行会的返回值赋值到vue实例的setupState属性上。07V28资讯网——每日最新资讯28at.com

看到这里我们整个流程已经可以串起来了,首先会执行由setup语法糖编译后的setup函数。然后将setup函数中由顶层变量和import导入组成的返回值对象赋值给vue实例的setupState属性,然后执行render函数的时候从vue实例中取出setupState属性也就是setup的返回值。这样在render函数也就是template模版就可以访问到setup中的顶层变量和import导入。07V28资讯网——每日最新资讯28at.com

现在我们可以回答前面提的另外两个问题了:07V28资讯网——每日最新资讯28at.com

为什么在setup顶层定义的变量可以在template中可以直接使用?07V28资讯网——每日最新资讯28at.com

因为在setup语法糖顶层定义的变量经过编译后会被加入到setup函数返回值对象__returned__中,而非setup顶层定义的变量不会加入到__returned__对象中。setup函数返回值会被塞到vue实例的setupState属性上,执行render函数的时候会将vue实例上的setupState属性传递给render函数,所以在render函数中就可以访问到setup顶层定义的变量和import导入。而render函数实际就是由template编译得来的,所以说在template中可以访问到setup顶层定义的变量和import导入。。07V28资讯网——每日最新资讯28at.com

为什么import一个组件后就可以直接使用,无需使用components 选项来显式注册组件?07V28资讯网——每日最新资讯28at.com

因为在setup语法糖中import导入的组件对象经过编译后同样也会被加入到setup函数返回值对象__returned__中,同理在template中也可以访问到setup的返回值对象,也就可以直接使用这个导入的组件了。07V28资讯网——每日最新资讯28at.com

总结

setup语法糖经过编译后就变成了setup函数,而setup函数的返回值是一个对象,这个对象就是由在setup顶层定义的变量和import导入组成的。vue在初始化的时候会执行setup函数,然后将setup函数返回值塞到vue实例的setupState属性上。执行render函数的时候会将vue实例上的setupState属性(也就是setup函数的返回值)传递给render函数,所以在render函数中就可以访问到setup顶层定义的变量和import导入。而render函数实际就是由template编译得来的,所以说在template中就可以访问到setup顶层定义的变量和import导入。07V28资讯网——每日最新资讯28at.com

07V28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-76516-0.htmlVue 3 的 setup 语法糖到底是什么东西?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 详解CSS中@keyframes:动画制作的艺术

下一篇: 一千个微服务之死

标签:
  • 热门焦点
  • 一加Ace2 Pro真机揭晓 钛空灰配色质感拉满

    终于,在经过了几波预热之后,一加Ace2 Pro的外观真机图在网上出现了。还是博主数码闲聊站曝光的,这次的外观设计还是延续了一加11的方案,只是细节上有了调整,例如新加入了钛空灰
  • 石头智能洗地机A10 Plus体验:双向自清洁治好了我的懒癌

    一、前言和介绍专为家庭请假懒人而生的石头科技在近日又带来了自己的全新旗舰新品,石头智能洗地机A10 Plus。从这个产品名上就不难看出,这次石头推出的并不是常见的扫地机器
  • 帅气纯真少年!日本最帅初中生选美冠军出炉

    日本第一帅哥初一生选美大赛冠军现已正式出炉,冠军是来自千叶县的宗田悠良。日本一直热衷于各种选美大赛,从&ldquo;最美JK&rdquo;起到&ldquo;最美女星&r
  • 一年经验在二线城市面试后端的经验分享

    忠告这篇文章只适合2年内工作经验、甚至没有工作经验的朋友阅读。如果你是2年以上工作经验,请果断划走,对你没啥帮助~主人公这篇文章内容来自 「升职加薪」星球星友 的投稿,坐
  • 共享单车的故事讲到哪了?

    来源丨海克财经与共享充电宝相差不多,共享单车已很久没有被国内热点新闻关照到了。除了一再涨价和用户直呼用不起了。近日多家媒体再发报道称,成都、天津、郑州等地多个共享单
  • 拼多多APP上线本地生活入口,群雄逐鹿万亿市场

    Tech星球(微信ID:tech618)文 | 陈桥辉 Tech星球独家获悉,拼多多在其APP内上线了&ldquo;本地生活&rdquo;入口,位置较深,位于首页的&ldquo;充值中心&rdquo;内,目前主要售卖美食相关的
  • 2纳米决战2025

    集微网报道 从三强争霸到四雄逐鹿,2nm的厮杀声已然隐约传来。无论是老牌劲旅台积电、三星,还是誓言重回先进制程领先地位的英特尔,甚至初成立不久的新
  • iQOO Neo8 Pro抢先上架:首发天玑9200+ 安卓性能之王

    经过了一段时间的密集爆料,昨日iQOO官方如期对外宣布:将于5月23日推出全新的iQOO Neo8系列新品,官方称这是一款拥有旗舰级性能调校的作品。随着发布时
  • 世界人工智能大会国际日开幕式活动在世博展览馆开启

    30日上午,世界人工智能大会国际日开幕式活动在世博展览馆开启,聚集国际城市代表、重量级院士专家、国际创新企业代表,共同打造人工智能交流平台。上海市副市
Top