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

相关推荐
小只笨笨狗~11 分钟前
el-dialog宽度根据内容撑开
前端·vue.js·elementui
weixin_4903543415 分钟前
Vue设计与实现
前端·javascript·vue.js
GISer_Jing1 小时前
React过渡更新:优化渲染性能的秘密
javascript·react.js·ecmascript
烛阴1 小时前
带你用TS彻底搞懂ECS架构模式
前端·javascript·typescript
wayhome在哪2 小时前
3 分钟上手!用 WebAssembly 优化前端图片处理性能(附完整代码)
javascript·性能优化·webassembly
卓码软件测评2 小时前
【第三方网站运行环境测试:服务器配置(如Nginx/Apache)的WEB安全测试重点】
运维·服务器·前端·网络协议·nginx·web安全·apache
龙在天2 小时前
前端不求人系列 之 一条命令自动部署项目
前端
开开心心就好2 小时前
PDF转长图工具,一键多页转图片
java·服务器·前端·数据库·人工智能·pdf·推荐算法
国家不保护废物2 小时前
10万条数据插入页面:从性能优化到虚拟列表的终极方案
前端·面试·性能优化
文心快码BaiduComate2 小时前
七夕,画个动态星空送给Ta
前端·后端·程序员