如何在 Vue 项目的 template 中使用 JSX

相信写过 JSX 的人再去写 Vue 中的 <template>,很多人会觉得束缚太多了,语法不够灵活。我也深有此感,所以琢磨了在 <template> 中使用 JSX 的一些方式,特此记录与大家分享。

两种写法的对比

先来看下常规的 <template> 的写法:

html 复制代码
<script setup lang="tsx">
import { reactive } from 'vue'
const state = reactive({
  status: null as '成功' | '失败' | '未知' | null,
  message: null as null | string,
})
MockAPI().then((data) => { /** 调用接口,填充 state 数据 */ })
</script>

<template>
  <div role="demo">
    <div v-if="!state.status">状态获取中...</div>
    <div
      v-else
      :class="{
        'text-success': state.status === '成功',
        'text-error': state.status === '失败',
        'text-warning': state.status === '未知',
      }"
    >
      <div role="content" v-if="!state.message">
        <span>状态:{{ state.status }}</span>
        <a-icon v-if="['成功', '失败'].includes(state.status)" :type="state.status === '成功' ? 'check' : 'close'" />
      </div>
      <!-- 失败且有失败原因时,展示失败原因 -->
      <a-tooltip v-else :title="state.message">
        <div role="content">
          <span>状态:{{ state.status }}</span>
          <a-icon v-if="['成功', '失败'].includes(state.status)" :type="state.status === '成功' ? 'check' : 'close'" />
        </div>
      </a-tooltip>
    </div>
  </div>
</template>

这是常见的在 <template> 中写所有的元素,可以对比下使用 <template> + JSX 的方式:

html 复制代码
<script setup lang="tsx">
// ...
const Node = {
  render() {
    if (!state.status) return <div v-if="!state.status">状态获取中...</div>

	  const clsMap = {
	    成功: 'text-success',
	    失败: 'text-error',
	    未知: 'text-warning',
	  }
    const showIcon = ['成功', '失败'].includes(state.status)
    const content = (
      <div role="content">
        <span>状态:{state.status}</span>
        {showIcon && <a-icon type={state.status === '成功' ? 'check' : 'close'} />}
      </div>
    )
    return (
      <div class={clsMap[state.status]}>
        {/* 使用 JSX,content 可以重用 */}
        {!state.message ? content : <a-tooltip title={state.message}>{content}</a-tooltip>}
      </div>
    )
  },
}
</script>

<template>
  <div role="demo">
    <component :is="Node" />
  </div>
</template>

通过对比,可以看到使用 <template> + JSX 的方式有如下优点:

  • 类似 hooks,将高度相关的函数、变量与元素耦合在一起,代码更简洁更好维护
  • 使用 JSX 实现更灵活的逻辑判断
  • 为数据命名有意义的变量名,如上面的 showIconclsMap,增加代码可读性
  • 借助 JS 编程减少重复代码,如上面提取的 div[role="content"] 元素

上面这种实现方式的原理很简单, <componet> 是 Vue 内置的组件, is 属性接收一个组件的定义, const Node = { render() {} } 其实就是一个组件内定义的选项式组件,使用 const Node = () => {} 函数式组件也是可以的。

自定义 组件实现

除此之外,自定义一个全局的 <jsx> 组件也是可以实现的:

tsx 复制代码
// Jsx.jsx
const Jsx = defineComponent({
  name: 'Jsx',
  props: {
    node: {
      // 这里的 Object 实际上特指 VNode 类型
      type: [Object, String, Number],
    },
    // Vue2 中的组件必须用实际的标签包裹
    tag: {
      type: String,
      default: 'div',
    },
  },
  setup(props) {
    // 注意:props.node 允许为空(null、undefined),这时还是会渲染出一个内容为空的节点
    return () => h(props.tag, [props.node as any])
  },
})

// vue 项目入口文件 main.ts
import Vue from 'vue'
Vue.component('Jsx', Jsx)

使用方式如下:

html 复制代码
<script setup lang="tsx">
function renderNode() {
  return <div>JSX 内容...</div>
}
</script>

<template>
	<jsx :node="renderNode()" />
</template>

不适用的场景

当然了,<template> + JSX 也并不适用于所有的场景。比如下面这几个场景

1. 逻辑简单

逻辑简单时,使用 <template> 即可,没必要为了用 JSX 而用 JSX。

2. 逻辑过于复杂

当逻辑过于复杂时,建议提取为一个单独的组件。

3. 想要更好的 Vue 编译优化、热更新

由于 <template> 是静态的,而 JSX 是动态的,所以 Vue 对前者的编译优化会更好些。此外经过我的实践, JSX 在很多时候热更新会状态丢失,稍稍影响到了开发体验。

相关推荐
jingling5558 小时前
vue | 在 Vue 3 项目中集成高德地图(AMap)
前端·javascript·vue.js
油丶酸萝卜别吃8 小时前
Vue3 中如何在 setup 语法糖下,通过 Layer 弹窗组件弹出自定义 Vue 组件?
前端·vue.js·arcgis
J***Q29215 小时前
Vue数据可视化
前端·vue.js·信息可视化
JIngJaneIL16 小时前
社区互助|社区交易|基于springboot+vue的社区互助交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·社区互助
ttod_qzstudio16 小时前
深入理解 Vue 3 的 h 函数:构建动态 UI 的利器
前端·vue.js
芳草萋萋鹦鹉洲哦16 小时前
【elemen/js】阻塞UI线程导致的开关卡顿如何优化
开发语言·javascript·ui
_大龄17 小时前
前端解析excel
前端·excel
1***s63217 小时前
Vue图像处理开发
javascript·vue.js·ecmascript
一 乐17 小时前
应急知识学习|基于springboot+vue的应急知识学习系统(源码+数据库+文档)
数据库·vue.js·spring boot
槁***耿17 小时前
JavaScript在Node.js中的事件发射器
开发语言·javascript·node.js