如何在 vue3 里使用 jsx 进行开发

2,621 阅读5分钟

前言

在大多数情况下,我们使用 Vue 都是用的 sfc 单文件组件模式,即一个组件就是一个文件。而且 Vue 也是默认推荐使用模板<template>写法的,但其实 Vue 也是支持使用 jsx 来编写组件的。

前段时间,树哥也尝试在 vue 的项目中使用 jsx 去进行开发,本篇也是对如何在 Vue 中使用 jsx 所做的一个总结。

什么是 jsx

jsx(JavaScript XML)是一种用于在 javaScript 中编写类似 XML 的语法扩展。

jsx 允许开发者在 JavaScript 代码中直接编写类似 HTML 的标记语法,以声明式地描述 UI 组件的结构和交互。通过使用 jsx,可以将 HTML 结构、组件逻辑和数据绑定等内容组合在一起,更加直观地描述页面结构和交互。

写 react 的小伙伴可能会很熟悉,那么如何在 vue 的项目中使用 jsx 呢,这里我们以 vue3 + vite 项目为例尝试在 vue 的项目中使用 jsx 。

默认的情况下,vue3+vite 的项目是不支持 jsx 的,如果想支持 jsx,需要安装插件@vitejs/plugin-vue-jsx

安装

npm i @vitejs/plugin-vue-jsx -D

然后在 vite.config.js 中进行下相应配置:

// 配置 vue 使用jsx
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [vue(), vueJsx()],
})

好了,基本的准备工作完成,那么我们就可以尝试在 Vue 的项目中使用 jsx 了

简单上手

<script lang="jsx">
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)

    const increment = () => {
      count.value++
    }

    return () => (
      <div>
        <h1>Counter: {count.value}</h1>
        <button onClick={increment}>Increment</button>
      </div>
    )
  },
}
</script>

script 中 lang 要改成 jsx

这里我们使用 jsx 实现了,点击按钮数字加一的功能

语法

插值

vue 模板语法中使用双大括号{{}},jsx 变为了单大括号 {},大括号内支持任何有效的 javaScript 表达式。

<h1>{{ msg }}</h1> // vue
<h1>{ msg }</h1> // jsx

指令

  • v-if

jsx 本身就有 js 的特性,所以不需要使用 v-if,使用 if/else 和三元表达式都可以实现相同作用

// 三元表达式
const isShow = ref(true)

return () => (
  <div>
    <div> {isShow.value ? 'Hello 啊' : '树哥'}</div>
  </div>
)
// if/else
const element = (name) => {
  if (name) {
    return <h1>Hello 啊, {name}</h1>
  } else {
    return <h1>Hello 啊</h1>
  }

return () => {
  return element('树哥')
}
  • v-show

jsx 中同样可以使用这个指令

setup () {
  const isShow = ref(true)
  const str = ref('Hello 啊,树哥!')
  return () =>
    <div v-show={isShow.value}>{str.value}</div>

}

这里需要注意的是,vue3 的 template 会自动解析 ref 的.value,而在 jsx 中 ref 的 .value 不会被自动解析,写的时候需要我们自己加上 .value

  • v-for

在做列表渲染的时候,我们常用 v-for 来实现,而在 jsx 中我们可以直接使用 js 的 map 方法

<!-- vue -->
<template>
  <div>
    <div>
      <span>姓名</span>
      <span>年龄</span>
    </div>
    <div v-for="(item, index) in listData" :key="index">
      <span>{{ item.name }}</span>
      <span>{{ item.age }}</span>
    </div>
  </div>
</template>

<script setup>
const listData = [
  { name: '树哥', age: 18 },
  { name: '张麻子', age: 20 },
  { name: '黄老爷', age: 16 },
]
</script>
// jsx
setup () {
  const listData = [
    { name: '树哥', age: 18 },
    { name: '张麻子', age: 20 },
    { name: '黄老爷', age: 16 }
  ]
  return () => (
    <div>
      <div>
        <span>姓名</span>
        <span>年龄</span>
      </div>
      {
        listData.map((item,index) => {
          return <div key={index}>
            <span>{item.name}</span>
            <span>{item.age}</span>
          </div>
        })
      }
    </div>
  )
}
  • v-model

jsx 中同样支持 v-model

<input v-model="value" /> // vue
<input v-model={value} /> // jsx

如果你需要绑定自定义名称,例如绑定 visible,JSX 中不能直接用 v-model:visible 的语法,需要传入一个数组[value, 'visible'],数组的第二个参数就是要绑定的自定义名称。

<input v-model:visible="value" /> // vue
<input v-model={[value,'visible']} /> // jsx

一些时候我们可能添加一些修饰符


<input v-model:visible.trim="value" /> // vue
<input v-model={[value,'visible',['trim']]} /> // jsx

class 与 style 绑定

class 类名绑定可以使用 js 模板字符串或者数组的形式

// 模板字符串
<div class={`name ${ isActive ? 'active' : '' }`}>class</div>

//数组形式
<div class={ [ 'name', isActive && 'active' ] } >class</div>

style 绑定需要使用双大括号

const color = 'red'
...
<div style={{ color, fontSize: '16px' }}>style</div>

事件绑定

事件绑定需要注意的是,要在事件名前面要加上 on 前缀,如 click 要写成 onClick,mouseenter 事件则是写成 onMouseenter。

const confirm = () => {
  // do sth ...
}

;<button onClick={confirm}>确定</button>

有时候我们需要给事件传递参数,这时候可以用箭头函数进行包裹

<button onClick={() => confirm('传递的参数')}>确定</button>

当我们需要给事件增加修饰符时,需要借助 withModifiers 方法

import { withModifiers, defineComponent, ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    const increment = () => {
      count.value++
    }

    return () => (
      <div onClick={withModifiers(increment, ['self'])}>{count.value}</div>
    )
  },
}

slot 插槽

jsx 中是没有 <slot> 标签的,定义插槽需要使用双大括号。

如果是具名插槽,则将 default 改成具名插槽的名称。

作用域插槽需要在函数里传入要传给插槽的参数。

setup(props, { slots }) { // 从ctx中解构出来 slots
  return () => (
    <div>
      <p>jsx 使用插槽</p>
      <p>默认插槽内容:{slots.default?.()}</p>
      <p>具名插槽内容:{slots.mySlot?.()}</p>
      <p>作用域插槽:{slots.main?.({item:'我是作用域插槽'})}</p>
    </div>
  )
}

在父组件中使用

import Child from './child.vue'

export default {
  components: { Child },
  setup() {
    return () => (
      <Child
        v-slots={{
          default: () => '默认插槽内容',
          mySlot: () => '具名插槽',
          main: (props) => props.item,
        }}></Child>
    )
  },
}

小结

我们在平常的项目开发中,多是使用 template 来进行开发,本文只是粗略的对在 vue 中使用 jsx 做了一个介绍或者说参考。

使用 template 的话:template 的语法是固定的,如 v-if、v-for 等。我们按照这种固定格式的语法书写, Vue3 在编译层面就可以很方便地去做静态标记的优化,减少Diff过程。比如静态提升,类型标记,树结构打平等来提高虚拟 DOM 运行时性能。这也是官方默认推荐的形式。

而使用 jsx 的,因为 jSX 可以看做是 h 函数的一个语法糖,其本质就是 JavaScript,相对的来说会更加的灵活,使用 jsx 可以支持更动态的需求。

我们在日常开发中应该选用那种呢,这个其实见仁见智了。个人觉得只是完成平常的业务开发的话,优先考虑使用 template, 当需要处理一些灵活的场景,更高动态性要求的时候,换句话说做组件封装的时候或许 jsx 是个不错的选择,如 ant-design-vue 就是用 jsx/tsx 来实现的。

参考

在 vue3 中优雅的使用 jsx/tsx
Vue3: JSX 更灵活的开发场景

往期回顾

15分钟学会 pnpm monorepo+vue3+vite 搭建组件库并发布到私有仓库(人人都能学会)
用微前端 qiankun 接入十几个子应用后,我遇到了这些问题
vue3 正式发布两年后,我才开始学 — vue3+setup+ts 🔥
2022年了,我才开始学 typescript ,晚吗?(7.5k字总结)
当我们对组件二次封装时我们在封装什么
vue 项目开发,我遇到了这些问题
关于首屏优化,我做了哪些