vue3的SFC中使用渲染函数和jsx的方式
我自己在 vue3+vite 项目中的单文件组件中使用 render 函数和 jsx 时候碰到的问题,在此记录。本文主要总结了如何在 vue3 里面使用渲染函数,总结了使用渲染函数的 n 种写法。
前言
vue3 的组件定义已经不再采用 class 或者 Vue.extend 等方式来实现组件类了。而是直接以一个组件对象定义的方式来定义组件。例如:
1 | const MyComponent = { |
这就是一个组件。注意,由于 vue 构建配置默认只针对 SFC 单文件组件里的 template 开启 template 字段的编译时构建,而运行时 vue 是缺少 template 编译器的,所以上述写法会导致运行时报错。
为了运行时能让浏览器中实时编译 template 字段,解决方案就是,在 vite.config.js 中把 vue 改成带编译器的版本:
1 | // vite 配置 |
一般来说,你会在 vue 单文件组件中编写上述组件,于是你可以把 template 单独拆出来,从而再编译时就进行 template 编译:
1 | // MyComponent.vue |
如果你硬要在选项里面手写一个 template: '<div></div>'
,应该 SFC 构建时候会用你外部 template 标签内的模板覆盖掉你选项里的 template。
选项式 API 的 render 写法
我们先看选项式 api 的 render 函数式模板写法。假设你在非单文件组件内要使用 render 函数,那么你就:
1 | // MyComponent.js |
如果你是 jsx 语法来编写渲染函数,那么你就把组件 文件名称改成 MyComponent.jsx。然后:
1 | // MyComponent.jsx或 tsx。 |
注意,默认 vite 可能不带 jsx 配置你可能需要打开一下子(参考网上教程即可)。
如果是单文件组件内编写 render 的话,从原理可知:
vue 构建时候会对 SFC 里的 template 标签进行编译,编译成 render 函数后覆盖掉你的选项中的 render 函数。
于是,你根本无法让你如下的 render 函数生效:
1 | // MyComponent.vue |
单文件组件中强行使用 render 函数
既然 SFC 里面 template 模板总是会编译成 render 并覆盖我们自己的 render,那我们换个思路解决:我把我的 render 函数当成一个组件,交给 template 渲染。
于是解决方案就是:
1 | <template> |
setup 组合式写法下如何使用渲染函数
上面的选项式看起来很美好,而且出现的小问题我也给解决了。但是如果采用 setup 组合式语法,或者采用 SFC 单文件组件的时候,事情就开始变得复杂了。
我们知道,组合式 api 主要使用 setup 钩子函数来定义组件逻辑。setup 函数中负责返回模板需要的所有内容给到模板。
那么,在不使用渲染函数的情况下,一个组件的定义是这么写的:
1 | // MyComponent.js |
注意:当你使用上述写法,依然需要你按照前文操作:把 vue 运行时 alias 改成带模板编译器的版本。
那么,假如我们需要把 template 选项,改成 render 函数。这里有 2 种方法。
方法 1:
咱们就废弃 template 字段,直接改成 render 字段就好:
1 | // MyComponent.js |
方法 2
setup 函数有个特点,他是在组件实例化之前执行。于是当 vue 在运行时实例化这个组件的 setup 的时候,发现 setup 返回了 render 函数,那么就不再使用外层你配置的其他 template 或 render 函数。
核心文档在这里:https://cn.vuejs.org/guide/extras/render-function.html#declaring-render-function
于是,我们可以这样写:
1 | // MyComponent.js |
如上写法下,实际渲染的时候,根本不会使用顶层 render 那个函数。而是渲染 setup 返回的 div。
setup 写法在单文件组件中怎么使用渲染函数
通过 setup 函数返回 render 函数
基于上文咱们说过的,当 vue 组件有 setup 的时候,且 setup 返回了一个渲染函数。那么 vue 渲染的时候会直接忽略外层其他的 render 函数或 template 模板。
于是,即使在单文件组件中你写了 template 标签—-此标签在构建后会变成当前组件最外层的 render 函数。那么,vue 在运行时也会完全忽略外层的任何渲染函数,转而会采用 setup 自身 return 的那个。
举例:
1 | // MyComponent.vue |
如上代码,是单文件组件内,已经编写了 template 标签模板。但 setup 函数会 return 一个渲染函数,于是最终渲染会忽略外部 template 模板,使用的是 setup 的返回的渲染函数。
直接在外层编写 render 函数
假设我们希望在 setup 函数并列的那一层去编写 render 函数,你会发现,在 SFC 中就又会被 SFC 的 template 编译后的 render 给覆盖掉了。最终会变成渲染 SFC 里面 template 标签里的模板内容。
解决方案,其实跟上文选项式 api 一样:就是我就直接利用 SFC 里面的 template 标签的编译得了。 那我把我得渲染函数变成一个组件,交给 template 去编译好吧。
于是,我们可以这么玩:
1 | <template> |
setup 函数 return 出来的东西,都会暴露到外层的 template(或者叫组件的 render 渲染函数中)。 所以我们可以把 RenderComponent
作为一个函数式组件,给 return 出去。
然后在 template 中,我们就去渲染这个自定义组件 RenderComponent
。 之所以要用 :is
这种动态渲染方式,是因为这里走的不是“组件注册”,而是“组件变量读取”。具体文档中有解释:https://cn.vuejs.org/api/sfc-script-setup.html#dynamic-components
SFC 中的 setup 语法糖怎么使用渲染函数
众所周知,vue SFC 里面你可以声明 <script setup lang="ts">
。这样你就不需要自己手工往外 return 东西了,你 script 标签内定义的所有变量,都会自动 return 到外部。那么在此情况下,如果你想直接让 setup 函数 return 一个渲染函数,从而废弃掉外部的 template 标签,要怎么实现呢?
答案是:无法实现。
于是我只能采用上一小节的方案:“搞一个自定义组件,交给 template 标签”。 于是解决方案如下:
1 | <template> |
杂项
tsx vscode 报错问题
在本文编写时候,有时候我并不太想手写渲染函数,所以我希望把 jsx/tsx 能力打开。我去看了下 vite 项目配置,本来就已经打开了:
1 | export default defineConfig({ |
但是当我编写 tsx 代码时候,某些位置还是会报错,例如:
1 | <template> |
上面代码中 hello
这个位置,会被 vscode 报错。报错内容是:Parsing error: Unterminated regular expression literal.eslint
。 看起来就是识别不了 jsx 语法啊好像。
百思不得其解,有搜不到好的答案。最后,我在 eslint.config.js 里面发现了猫腻, eslint 配置中有这么一段注释:
1 | // To allow more languages other than `ts` in `.vue` files, uncomment the following lines: |
原来,要把针对 vue 的 ts/tsx 检测适配给打开。于是把注释打开后,就变好了。
总结 vue3 中组件到底有几种定义方法
普通对象形式定义
1 | const MyComponent = { |
defineComponent
文档在这里:https://cn.vuejs.org/guide/typescript/overview.html#definecomponent
总之,传给他的第一个参数,可以是上一节我说的一个组件定义对象。然后他主要是提供类型推导能力。
另外,他也可以接收函数式组件,即 () => h()
这种。关于这一点,在他的 api 文档中有讲:https://cn.vuejs.org/api/general.html#function-signature
或者,也可以理解为,他第一个参数除了传递组件对象,也可以传递 setup 函数—-但要求是 return render 函数的那种 setup 函数。 (其实这种 setup 函数,基本上就很像纯函数式组件了)。