Vue的不同风格写法,你知道么?
引入
打开 Vue3 的官方文档,它首先会告诉你,Vue 的组件可以按两种不同的风格书写:选项式 API 和组合式 API。文档为我们提供一系列两种风格的代码参考,供我们按照偏好进行选择。
实际上,Vue3 组件可不止两种写法,而是多达十几种!然而,不管是什么写法,它们都是基于同一个底层系统实现的,概念之间也是彼此相通的,只是使用的接口不同。在实际开发中,我们也不会同时使用到那么多种写法,但是这并不意味着我们不需要去了解这些写法!
setup 语法糖
setup 语法糖应该是最常用的写法了。在 Vue3 中,我们想封装一个组件,最习惯的做法还是新建一个 Vue 文件,并将组件代码写在文件中。具体是:页面结构写在 template 中,页面逻辑写在 script 中,页面样式写在 style 中。
总之,我们将与该组件相关的代码都写在一起、放在一个文件中单独维护,在需要该组件的地方引入使用。
这里我们使用了 setup 语法糖,直接在 script 中书写我们的 setup 内部的逻辑。
1 | <template> |
在 App. vue 中引入并使用:
1 | // App.vue |
注:后续写法尽管形式不同,但它们最终的目的都是导出一个组件,所以对于组件使用方来说(这里是 App. vue),怎么使用这个组件的代码都是不变的,所以将不再重复此代码。
Vue2 选项式写法
Vue2 经典写法
这种写法也是比较经典的。和 setup 语法糖写法类似。我们需要新建一个 vue 文件来存储我们的组件代码,然后在需要使用该组件的地方对其进行引入。区别在于,我们需要在 script 中导出一个 Vue 实例。
这里我们导出的其实是一个普通对象,该对象包含 data、methods 等属性。这个对象的属性都是可选的,即 option,翻译回来即“选项”。
1 | <template> |
defineComponent 辅助函数
尽管我们在 script 语言块中导出的默认对象会被 vue 编译器当成 vue 实例,但不管怎么看,它依旧只是一个 plain object。在定义组件实例方面,vue 提供了一个名为 defineComponent 辅助接口。
1 | <template> |
尽管这个接口也不能改变我们导出的是一个普通对象的事实,但是它可以为我们的实例提供强大的类型推导。我们可以把它看成是一个返回 vue 实例的工厂函数,让我们的代码看起来更加规范。
Vue3 选项式写法
在 Vue3 中,官方引入了新的选项 setup,这是 Vue3 选项式写法和 Vue2 写法的主要区别。setup 选项的意义在于它允许我们在选项式的写法中引用和使用组合式的 api,比如 onMounted、ref、reactive 等。但对于我们来说,它对于我们有益的地方还是基于它封装起来的 setup 语法糖用起来很方便。
1 | <template> |
使用 defineComponent 时,它能够提示我们 setup 将会接收到什么参数:
1 | <template> |
以上写法我们都是在 template 上书写我们的页面结构,这也是最常见的几种写法,下面我们来介绍几种了解 vue 底层必不可少的写法,渲染函数。
手写渲染函数
template 模板语法本质上也可以算是一种语法糖。在 vue 编译器上,template 中的内容最终会被翻译为渲染函数,挂载到 vue 实例的 render 属性上。当需要渲染组件时,vue 就执行一次 render,得到对应的虚拟节点树,最后再转变为真实 dom。
Vue 允许我们脱离 template,直接自己书写渲染函数。位置就在导出实例的 render 选项上:
1 | <script lang="ts"> |
在 template 中,我们使用类似 html 的模板语法来描述我们的视图,在 render 函数中又如何描述呢?vue 提供了两个 api:createVnode 和 h。二者没有区别,h 函数只是 createVnode 的缩写。有了 render 函数,我们就不需要写 template 了。
1 | <script lang="ts"> |
在上面的示例中,我们使用 h 函数生成了一个 vNode,并 return 出去,作为本组件最终在被使用时渲染出来的效果。
在 template 中我们可以使用 v-if、v-for、slot 等模板语法,在 h 函数中这些概念也是支持的,只是形式不同,这方面官方文档有具体的示例。总之,template 模板和 render 选项是可以相互替代的。
setup 返回渲染函数
setup 返回 render 方法
一般来说,在选项式语法中,setup 方法返回一个对象,该对象暴露给 template,供 template 使用,具体参考第三个例子(vue3 选项式写法)。如果我们不使用 template,也就没有返回对象的必要了。
在 Vue3 中,还有另外一种不使用 template 的写法,就是在 setup 方法中返回一个 render 方法。
1 | <script lang="ts"> |
注意:
- 在选项式中使用 setup 之后,一般不应该再使用 data、生命周期等在选项式写法中常用的选项,而应该把主要逻辑都写在 setup 中,并适当引入组合式的 api。比如,使用 ref,而不是 data 选项。
- ref 自动解包是 template 特有的功能,h 函数是没有这个功能的。在 h 函数中引入 ref,记得理所当然地带上
.value
。
defineComponent 传入setup
就注意中的第一点,我们可以采用下面这种写法:直接在 defineComponent 中书写 setup 函数(如果再省一点就是 setup 语法糖的写法了)。
1 | <script lang="ts"> |
以上就是渲染函数的写法,是不是有点感觉了呢,一下子就学会了两个 api!后面会提到的 Jsx 写法其实也应该归为渲染函数写法的一种(只要不是 template,而是用 JavaScript 表达页面结构的,都是渲染函数),但是相对于 h 函数,jsx 并不是纯粹的 js,所以我将它们分成了两类。
Vue & Jsx
在render 中使用 jsx
有了前面两类写法介绍的铺垫,接下来引入 jsx 语法就没有什么难理解的点了。
jsx 在 vue 文件中是这样写的。在 render 渲染函数返回值处书写 jsx 替代 h 函数。书写纯 JavaScript 的 h 函数描述结构还是比较繁冗的,jsx 就是简化了的h 函数写法。
1 | <script lang="tsx"> |
在 setup 中使用jsx
jsx 和 setup 配合食用更加。在选项式风格中使用 setup,在 setup 中使用组合式 api,并且返回 jsx 书写的渲染函数。
1 | <script lang="tsx"> |
defineComponent 简写
这个其实就是前面介绍过的 「defineComponent 传入 setup」 函数写法:这里的区别只是使用 jsx 替代了 h 函数。
1 | <script lang="tsx"> |
自行导出 vNode 对象
我们也可以自己将 render 函数执行一遍,然后将得到的 jsx Element 导出,和上一个示例「defineComponent 简写」是十分相似。但是这段代码的缺点非常致命,它不支持接收外部传递来的属性参数。
1 | <script lang="tsx"> |
不要使用这种写法。这里会提到这样写,只是因为和后面的「函数式组件(其二)」 写法有关联。本写法与其它写法都不同,其它写法导出的都是 JavaScript 对象或者 jsx 对象,而这里我们则是自己执行了一遍渲染函数并得到了虚拟节点,直接将虚拟节点导出去。既然都已经把虚拟节点创建出来了,那自然无法接收 props。
defineComponent 的第二个参数
如果 defineComponent 的第一个参数是 setup 函数,那么它的第二个参数则可以为组件的定义添加需要的选项,但一般除了补充 props 选项,不会再需要其它选项了(组合式 api 和 setup 的入参可以完全替代其它选项)。
1 | <script lang="tsx"> |
直接在 vue 中使用 jsx
这里 jsx 不再只作为返回值,而是直接被某处使用。它可以是被直接导出,或者用在 template 上。
直接导出 jsx 对象
直接将 jsx 对象导出使用。比前面的写法更简洁,做法就是把 setup 里面的内容提到外面。这里需要注意的是我们导出的是一段直接的 jsx 对象(jsx Element),而不是渲染函数。
1 | <script lang="tsx"> |
直接用在 template 上
这种写法可以帮助你在自身的组件内复用一些颗粒度更小的组件,它和 setup 语法糖的写法非常接近,只是 User 变量可以作为标签直接使用。
1 | <template> |
函数式组件(其一)
你还可以将 User 写成函数式组件,在本页面内使用。但它不会将连字符属性转换为小驼峰写法。这和「直接用在 template 上」的内容都是一样的,它们都是为了方便在组件本身复用一些常用的组件。
1 | <template> |
如果你经常使用 tailwind,你可能就会知道什么情况下会出现小颗粒度的可复用标签,比如,一个加了一大堆类名的 div 标签。
独立的 Jsx 文件
以上介绍的所有写法,都是在 .vue 文件中书写的,而且也离不开