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

相关推荐
光影少年5 小时前
angular生态及学习路线
前端·学习·angular.js
記億揺晃着的那天7 小时前
Vue + Element UI 表格自适应高度如何做?
javascript·vue.js·ui
无尽夏_7 小时前
HTML5(前端基础)
前端·html·html5
Jagger_7 小时前
敏捷开发流程-精简版
前端·后端
FIN66687 小时前
昂瑞微冲刺科创板:创新驱动,引领射频芯片国产化新征程
前端·安全·前端框架·信息与通信·芯片
GISer_Jing7 小时前
ByteDance——jy真题
前端·javascript·面试
睡美人的小仙女1277 小时前
浏览器为何屏蔽本地文件路径?
前端
真的想不出名儿7 小时前
Vue 中 props 传递数据的坑
前端·javascript·vue.js
FIN66687 小时前
昂瑞微:深耕射频“芯”赛道以硬核实力冲刺科创板大门
前端·人工智能·科技·前端框架·信息与通信·智能
阳光阴郁大boy7 小时前
星座运势网站技术解析:从零打造现代化Web应用
前端·javascript