如何在 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 在很多时候热更新会状态丢失,稍稍影响到了开发体验。

相关推荐
万少2 小时前
HarmonyOS 开发必会 5 种 Builder 详解
前端·harmonyos
橙序员小站4 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
炫饭第一名6 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
王晓枫7 小时前
flutter接入三方库运行报错:Error running pod install
前端·flutter
符方昊7 小时前
React 19 对比 React 16 新特性解析
前端·react.js
ssshooter7 小时前
又被 Safari 差异坑了:textContent 拿到的值居然没换行?
前端
曲折7 小时前
Cesium-气象要素PNG色斑图叠加
前端·cesium
Forever7_7 小时前
Electron 淘汰!新的桌面端框架 更强大、更轻量化
前端·vue.js
不会敲代码17 小时前
前端组件化样式隔离实战:React CSS Modules、styled-components 与 Vue scoped 对比
css·vue.js·react.js
Angelial7 小时前
Vue3 嵌套路由 KeepAlive:动态缓存与反向配置方案
前端·vue.js