• 有点儿神奇,原来vue3的setup语法糖中组件无需注册因为这个


    前言

    众所周知,在vue2的时候使用一个vue组件要么全局注册,要么局部注册。但是在setup语法糖中直接将组件import导入无需注册就可以使用,你知道这是为什么呢?注:本文中使用的vue版本为3.4.19

    关注公众号:【前端欧阳】,给自己一个进阶vue的机会

    看个demo

    我们先来看个简单的demo,代码如下:

    <template>
      <Child />
    template>
    
    <script lang="ts" setup>
    import Child from "./child.vue";
    script>
    

    上面这个demo在setup语法糖中import导入了Child子组件,然后在template中就可以直接使用了。

    我们先来看看上面的代码编译后的样子,在之前的文章中已经讲过很多次如何在浏览器中查看编译后的vue文件,这篇文章就不赘述了。编译后的代码如下:

    import {
      createBlock as _createBlock,
      defineComponent as _defineComponent,
      openBlock as _openBlock,
    } from "/node_modules/.vite/deps/vue.js?v=23bfe016";
    import Child from "/src/components/setupComponentsDemo/child.vue";
    
    const _sfc_main = _defineComponent({
      __name: "index",
      setup(__props, { expose: __expose }) {
        __expose();
        const __returned__ = { Child };
        return __returned__;
      },
    });
    
    function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
      return _openBlock(), _createBlock($setup["Child"]);
    }
    
    _sfc_main.render = _sfc_render;
    export default _sfc_main;
    

    从上面的代码可以看到,编译后setup语法糖已经没有了,取而代之的是一个setup函数。在setup函数中会return一个对象,对象中就包含了Child子组件。

    有一点需要注意的是,我们原本是在setup语法糖中import导入的Child子组件,但是经过编译后import导入的代码已经被提升到setup函数外面去了。

    在render函数中使用$setup["Child"]就可以拿到Child子组件,并且通过_createBlock($setup["Child"]);就可以将子组件渲染到页面上去。从命名上我想你应该猜到了$setup对象和上面的setup函数的return对象有关,其实这里的$setup["Child"]就是setup函数的return对象中的Child组件。至于在render函数中是怎么拿到setup函数返回的对象可以看我的另外一篇文章: Vue 3 的 setup语法糖到底是什么东西?

    接下来我将通过debug的方式带你了解编译时是如何将Child塞到setup函数的return对象中,以及怎么将import导入Child子组件的语句提升到setup函数外面去的。

    compileScript函数

    在上一篇 有点东西,template可以直接使用setup语法糖中的变量原来是因为这个 文章中我们已经详细讲过了setup语法糖是如何编译成setup函数,以及如何根据将顶层绑定生成setup函数的return对象。所以这篇文章的重点是setup语法糖如何处理里面的import导入语句。

    还是一样的套路启动一个debug终端。这里以vscode举例,打开终端然后点击终端中的+号旁边的下拉箭头,在下拉中点击Javascript Debug Terminal就可以启动一个debug终端。
    debug-terminal

    然后在node_modules中找到vue/compiler-sfc包的compileScript函数打上断点,compileScript函数位置在/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js。接下来我们来看看简化后的compileScript函数源码,代码如下:

    function compileScript(sfc, options) {
      const ctx = new ScriptCompileContext(sfc, options);
      const setupBindings = Object.create(null);
      const scriptSetupAst = ctx.scriptSetupAst;
    
      for (const node of scriptSetupAst.body) {
        if (node.type === "ImportDeclaration") {
          // 。。。省略
        }
      }
    
      for (const node of scriptSetupAst.body) {
        // 。。。省略
      }
    
      let returned;
      const allBindings = {
        ...setupBindings,
      };
      for (const key in ctx.userImports) {
        if (!ctx.userImports[key].isType && ctx.userImports[key].isUsedInTemplate) {
          allBindings[key] = true;
        }
      }
      returned = `{ `;
      for (const key in allBindings) {
        // ...遍历allBindings对象生成setup函数的返回对象
      }
    
      return {
        // ...省略
        content: ctx.s.toString(),
      };
    }
    

    我们先来看看简化后的compileScript函数。

    compileScript函数中首先使用ScriptCompileContext类new了一个ctx上下文对象,在new的过程中将compileScript函数的入参sfc传了过去,sfc中包含了