平时开发 Vue
项目时,用的最多的就是模板组件,一些模板组件实现起来稍微麻烦的需求,也会用到渲染函数,对于 JSX/TSX 的应用相比之下就少了很多,不知大家是否也是如此呢?不过没关系,今天我们来详细的梳理下如何在 Vue
项目中使用 JSX
。建议跟着文章敲起来,为自己的技术道路添砖加瓦😄。
阅读本文您将收获:
- 了解
Vue3
如何使用JSX/TSX
。- 封装个业务弹窗,体验下
JSX/TSX
在实际开发中的应用。- 等等…
JSX
是一种类似 XML
的 JavaScript
扩展,本身没有特定的语义。 JavaScript
引擎和浏览器没有直接实现它,也没有纳入 ECMAScript
规范,核心作用就是被各种编译器(如 Babel
)解析,转换成标准的 JavaScript
代码。
简单来说,JSX
被设计为一个中间格式,用于在 JavaScript
中方便地编写类似 HTML
的结构,最终通过工具(如 Babel
)转换成标准的 JavaScript
代码,以在浏览器或 JavaScript
引擎中运行。
JSX
代码示例:
1const vnode = <div>hello</div>
启用 JSX
脚手架
目前 create-vue 和 Vue CLI 脚手架工具创建的模板工程,都提供了预置的 JSX
支持选项。
手动配置
若要手动配置,可使用 @vue/babel-plugin-jsx 插件。
安装依赖
终端输入以下命令,回车执行:
1npm install @vue/babel-plugin-jsx -D
配置 babel
babel 配置文件,如 babel.config.js
中添加如下配置:
1{
2 "plugins": ["@vue/babel-plugin-jsx"]
3}
Vite 配置
若项目使用 Vite 构建,需配置 ViteJSX 插件 - @vitejs/plugin-vue-jsx,否则无法识别 JSX
语法,会报解析错误。
安装插件
终端输入以下命令,回车执行:
1npm install @vitejs/plugin-vue-jsx -D
配置插件
Vite 配置文件,vite.config.ts 添加如下配置:
1import vue from '@vitejs/plugin-vue'
2import { defineConfig } from 'vite'
3import vueJsx from '@vitejs/plugin-vue-jsx'
4
5
6export default defineConfig({
7 plugins: [
8 vue(),
9 vueJsx(),
10 ]
11})
TSX
TS 配置
若项目使用 TypeScript
,Vue
也为 TSX
提供了类型推断。
tsconfig.json
compilerOptions
选项中添加如下配置:
1{
2 "jsx": "preserve"
3}
这样 TypeScript
会保留 JSX
语法,以便 Vue
的 JSX 转换器 将其转换为 Vue
渲染函数。
类型
-
Vue3.4 之前的版本,
Vue
会自动注册全局 JSX 命名空间,处理JSX
相关的类型定义,无需手动处理。 -
Vue3.4 版本之后,为减少全局污染,不再自动注册全局 JSX 命名空间,需要手动配置
TypeScript
来支持JSX
。tsconfig.json
compilerOptions
选项中添加如下配置:1{ 2 "jsx": "preserve", 3 "jsxImportSource": "vue", 4}
OK、JSX
的环境准备工作到此结束。
常见用法
列举 Vue
相关语法的 JSX
版本。
指令
v-if
JSX
中使用三元表达式实现 v-if 执行效果:
1<div>{ok.value ? <div>yes</div> : <span>no</span>}</div>
v-show
可以在 JSX
结构中直接使用:
1<input v-show={this.visible} />;
v-for
JSX
中使用 Array.map 实现 v-for 指令遍历效果:
1<ul>
2 {items.value.map(({ id, text }) => {
3 return <li key={id}>{text}</li>
4 })}
5</ul>
v-model
Vue 模板组件中的 v-model 指令有以下几种用法:
- 直接使用:
v-model="value"
- 带参:
v-model:title="title"
- 带修饰符:
v-model.trim="input"
- 带参 + 修饰符:
v-model:title.trim="title"
针对以上情况,我们分别来看下 JSX
如何实现:
- 直接使用
1<input v-model={val} />
- 带参
1<input v-model:title={title} />
- 带修饰符
1<input v-model_trim={input} />
或
1<input v-model={[input, ['trim']]} />
- 带参 + 修饰符
1<input v-model:title_trim={title} />
或
1<input v-model={[title, 'title', ['trim']]} />
事件 v-on
JSX
中以 on + 大写字母开头的属性名作为事件监听器。如 onClick 相当于模板中的 @click。
1<button
2 onClick={(event) => {
3
4 }}
5>
6 Click Me
7</button>
修饰符
按 JSX
处理方式划分,可把事件修饰符分为两类:
-
.passive
,.capture
,.once
这三个修饰符可以用驼峰命名法连接到事件名称后,作为事件监听器。
1<input 2 onClickCapture={() => {}} 3 onKeyupOnce={() => {}} 4 onMouseoverOnceCapture={() => {}} 5/>
-
除以上三个修饰符之外的,如
.self
使用 withmodifiers 帮助函数处理。
1<div onClick={withModifiers(() => {}, ['self'])} />
插槽
渲染插槽
我们可以通过 setup
函数上下文获取 slots
对象,其包含要渲染的所有插槽。假设现有组件 A,需要渲染 footer 插槽,可以如下方式渲染:
1export default {
2 props: ['message'],
3 setup(props, { slots }) {
4 return <div>{slots.footer({ text: props.message })}</div>
5 }
6}
传递插槽
向组件传递插槽时,有以下几种方式:
- v-slots
注意:是
v-slots
,不是v-slot
将插槽对象传递给 v-slots
指令。
1export default {
2 setup(props, { slots }) {
3 const slots = {
4 footer: () => <span>B</span>,
5 };
6 return () => (
7 <A v-slots={slots}></A>
8 );
9 }
10}
- 对象插槽
将插槽对象作为组件的子内容传递。
1export default {
2 setup(props, { slots }) {
3 const slots = {
4 footer: () => <span>B</span>,
5 };
6 return () => (
7 <A>{slots}</A>
8 );
9 }
10}
接下来我们用 JSX
封装一个基于 ElementPlus
Dialog
的业务弹窗组件 - zm-dialog
,整合弹窗标题,主体内容和底部操作按钮等主要内容。
1. 创建组件
创建 zm-dialog.tsx
文件,新增以下初始内容:
1import { ElDialog } from 'element-plus'
2
3export const ZmDialog = defineComponent({
4 name: 'ZmDialog',
5 setup(props) {
6 return {}
7 },
8 render() {
9 return <ElDialog></ElDialog>
10 }
11})
最基础的组件框架,没有任何内容。
2. 属性&事件
思考下组件的属性设计,考虑到大家对应 ElementPlus
组件已经十分熟悉,从降低上手难度的角度来说,我们应尽量沿用 ElDialog
组件的属性设计,并在此基础上,添加支撑业务组件的扩展属性。
属性包括但不限于以下:
model-value/v-model
:控制弹窗显隐。title
:弹窗标题。width
:弹窗宽度。fullscreen
:弹窗是否全屏。top
:弹窗距离顶部的 css 距离(margin-top)。modal
:是否需要遮罩。append-to-body
:是否插入至 body 元素。- …
cancelButtonText
: 取消按钮展示文本,默认“取消”。confirmButtonText
:确认按钮展示文本,默认“确定”。- …
事件除了沿用 ElDialog
的事件外,额外新增 cancel
、confirm
事件,点击对应按钮时触发。
1import { dialogProps, ElDialog } from 'element-plus'
2
3const zmDialogProps = {
4 ...dialogProps,
5
6 cancelButtonText: {
7 type: String,
8 default: '取消'
9 },
10
11 confirmButtonText: {
12 type: String,
13 default: '确定'
14 }
15}
16
17const zmDialogEmits = ['confirm', 'cancel']
3. 结构设计
弹窗结构包括以下三部分:
-
顶部标题栏:
包括标题及关闭按钮。
-
主体内容:
包括弹窗需要展示的主体内容,根据业务需求定义。
-
底部操作栏:
包括取消、确认操作按钮。
本部分涉及到插槽的定义:
- 标题插槽
header
:对应 ElDialog 的 header 插槽。 - 主体内容插槽
default
:对应 ElDialog 的 default 插槽。 - 底部栏插槽
footer
:对应 ElDialog 的 footer 插槽,默认展示”取消“、”确认“按钮。
1 * 弹窗组件
2 * 支持 Dialog 和 Drawer
3 */
4import { dialogProps, ElButton, ElDialog } from 'element-plus'
5import type { ExtractPropTypes } from 'vue'
6
7const zmDialogProps = {
8 ...dialogProps,
9
10 cancelButtonText: {
11 type: String,
12 default: '取消'
13 },
14
15 confirmButtonText: {
16 type: String,
17 default: '确定'
18 }
19}
20
21const zmDialogEmits = ['confirm', 'cancel']
22
23export const ZmDialog = defineComponent({
24 name: 'ZmDialog',
25 props: zmDialogProps,
26 emits: zmDialogEmits,
27 setup(props, { emit }) {
28 const visible = computed({
29 get() {
30 return props.modelValue
31 },
32 set(val) {
33 emit('update:modelValue', val)
34 }
35 })
36
37 const elDialogProps = computed(() =>
38 Object.keys(dialogProps).reduce((pre: any, cur: any) => {
39 pre[cur] = props[cur]
40 return pre
41 }, {})
42 )
43
44 const onCancel = () => {
45 visible.value = false
46 emit('cancel')
47 }
48
49 const onConfirm = () => {
50 emit('confirm')
51 }
52
53 return {
54 visible,
55 elDialogProps,
56 onCancel,
57 onConfirm
58 }
59 },
60 render() {
61 const { onCancel, onConfirm, cancelButtonText, confirmButtonText } = this
62 const footer = this.$slots.footer
63 ? this.$slots.footer
64 : () => (
65 <div>
66 <ElButton onClick={onCancel}>{cancelButtonText}</ElButton>
67 <ElButton type="primary" onClick={onConfirm}>
68 {confirmButtonText}
69 </ElButton>
70 </div>
71 )
72
73 const slots = {
74 header: this.$slots.title,
75 default: this.$slots.default,
76 footer
77 }
78 return (
79 <ElDialog
80 {...this.elDialogProps}
81 v-model={this.visible}
82 v-slots={slots}
83 ></ElDialog>
84 )
85 }
86})
4. 组件应用
zm-dialog 暂且封装到这,还有很多可以完善的地方,不过作为示例够用了,现在来测试一下。
新增测试页面,编写如下代码:
1<template>
2 <zm-dialog
3 v-model="visible"
4 title="测试弹窗"
5 @opened="handleOpened"
6 @cancel="handleCancel"
7 @confirm="handleConfirm"
8 >
9 hello dialog
10 </zm-dialog>
11</template>
12
13<script setup lang="ts">
14const visible = defineModel({ type: Boolean, default: false })
15
16const handleOpened = () => {
17 console.log('Opened')
18}
19
20const handleCancel = () => {
21 console.log('canceled')
22}
23
24const handleConfirm = () => {
25 console.log('confirmed')
26}
27</script>
本文重点介绍了 Vue3
使用 JSX/TSX
的开发方式,比较详细的讲述了 Vue
常用语法对应的 JSX
写法,并动手实践,一起封装业务弹窗组件,旨在帮助同学们加深对于 JSX
在 Vue
项目中的应用理解。希望对您有所帮助!相关代码已上传至 GitHub