Vue 3中JSX/TSX最佳实践

在 Vue 中使用 JSX/TSX 语法有利有弊,我们在这里不详细讨论,是否使用 JSX/TSX 取决于你的项目需求和个人偏好。本文主要介绍如何使用它,因为与 SFC(单文件组件)相比有一些不同,相信你看完后能够快速上手。

Vite 开始

首先要安装插件@vitejs/plugin-vue-jsx

shell 复制代码
npm install -D @vitejs/plugin-vue-jsx

然后在vite.config.ts文件中进行配置:

ts 复制代码
import { defineConfig } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [vueJsx()],
})

接下来就可以开始编写代码了,推荐使用.tsx后缀来创建组件,例如App.tsx

jsx 复制代码
import { defineComponent } from 'vue'

export default defineComponent({
  setup() {
    return () => <div>Hello Vue!</div>
  },
})

这里的defineComponent只是起到类型推导的作用,你可以省略它。当然,你也可以在.vue后缀的组件中的<script>标签中进行编写,但是这种写法仍然需要在vite.config.ts中配置插件@vitejs/plugin-vue。当引入.tsx后缀的组件时,可以省略后缀名,例如:

ts 复制代码
import { createApp } from 'vue'
import App from './App'

createApp(App).mount('#app')

CSS Modules

由于不是 SFC 组件,因此我们使用 CSS Modules 的方式来编写作用域样式。在使用 CSS Modules 时,我们只需要将文件名后缀改为.module.css,Vite 已经内置了对这类文件的处理。例子如下:

css 复制代码
.app {
  color: red;
}

引入:

jsx 复制代码
import { defineComponent } from 'vue'
import styles from './app.module.css'

export default defineComponent({
  setup() {
    return () => <div class={styles.app}>Hello Vue!</div>
  },
})

插值

在 SFC 中使用双花括号{{}}来进行插值,而在 JSX 中使用单花括号{},而且属性外层也不再需要引号。

jsx 复制代码
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const text = ref('Hello Vue!')
    return () => <div style={{ width: '100px' }}>{text.value}</div>
  },
})

在这里,与 SFC 中不同的是,使用ref变量时需要添加.value。获取节点也会有所变化,需要进行修改:

jsx 复制代码
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const text = ref('Hello Vue!')
    const el = ref()

    return () => (
      <div ref={el} style={{ width: '100px' }}>
        {text.value}
      </div>
    )
  },
})

需要注意的是,在这里使用ref来获取节点时不需要再加上.value,这与插值又是不一样的。

条件渲染(v-if)

JSX 中不能使用 v-ifv-else,而是使用三元运算符或 && 运算符进行替代。

jsx 复制代码
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const show = ref(true)

    return () => (show.value ? <div>是</div> : <div>否</div>)
  },
})

如果不需要不同条件返回值,可以使用 && 运算符:

jsx 复制代码
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const show = ref(true)

    return () => show.value && <div>是</div>
  },
})

列表循环(v-for)

列表循环同样不能使用,而应该用map替代。

jsx 复制代码
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const list = ref([1, 2, 3, 4])

    return () => (
      <>
        {list.value.map((item) => (
          <div>{item}</div>
        ))}
      </>
    )
  },
})

需要注意的是,JSX 必须要有一个根节点来包含所有的节点。当然,你可以使用空标签<>作为占位符,实际上不会生成该标签。为什么要使用map函数呢?因为它可以改变数组,并生成相应的 JSX。当然,你也可以使用forEach方法并定义一个函数来返回 JSX,就像下面这样:

jsx 复制代码
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const list = ref([1, 2, 3, 4])

    const renderList = () => {
      const jsxElements: JSX.Element[] = []
      list.value.forEach((item) => {
        jsxElements.push(<div>{item}</div>)
      })
      return jsxElements
    }

    return () => <>{renderList()}</>
  },
})

相比,这种写法变得复杂了。不过,使用函数返回 JSX 的方式仍然非常有用,特别是在编写递归组件时。你只需要定义一个函数就可以了,比 SFC 更加直观。

v-model

v-model在 SFC 中的写法与正常情况下差不多,可以直接使用v-model来绑定数据。不过修饰符和指定参数的写法略有不同,在 JSX 中不能使用.:。例如,修饰符.trim可以改成_trim

jsx 复制代码
<input type="text" v-model_trim="{current.value}" />

在 Vue 3 中,可以通过指定一个参数来将其作为prop传递给组件,例如v-model:title,同样这个参数也可以使用下划线表示。

jsx 复制代码
<input type="text" v-model_title="{current.value}" />

另外,也可以使用一个数组表示传入的值和名称。

jsx 复制代码
<input type="text" v-model={[current.value, 'title']} />

事件绑定

事件绑定使用on[事件名]的格式,如input事件使用onInput

jsx 复制代码
<input
  type="text"
  onInput={() => {
    console.log('input')
  }}
/>

自定义事件value-input

jsx 复制代码
<Input
  onValue-input={(v) => {
    console.log('input', v)
  }}
/>

事件修饰符同样不能用.,可以改为下划线或者驼峰式。

jsx 复制代码
<button onClick_stop={() => { console.log('click') }}>点击</button>
<button onClickStop={() => { console.log('click') }}>点击</button>

props

setup函数包含两个参数,第一个是props,用于接收父组件传递的数据。第二个参数是ctx对象,它包含了emitslotsattrs属性。JSX 中不能使用 SFC 的definePropsdefineEmits等函数,而是直接使用setup函数的参数。

下面是一个例子,展示了如何使用props

jsx 复制代码
import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    title: String,
  },
  setup(props) {
    return () => <h1>{props.title}</h1>
  },
})

插槽

插槽的定义不再使用<slot>标签,而是使用setup函数中的第二个参数slots。例如,定义默认插槽可以如下所示:

jsx 复制代码
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Content',
  setup(props, { slots }) {
    return () => <div>{slots.default?.()}</div>
  },
})

在父组件中使用时,可以像这样调用:

jsx 复制代码
<Content>
  <div>内容</div>
</Content>

定义具名插槽的方式也相同,例如命名为title的具名插槽可以这样写:

jsx 复制代码
<h2>{slots.title?.()}</h2>

使用具名插槽时,需要在父组件传入一个对象,其中 key 为插槽的名称,例如:

jsx 复制代码
<Content>
  {{
    title: () => <h2>标题</h2>,
    default: () => <div>内容</div>,
  }}
</Content>

然而这种写法可能存在一些问题,花括号的外面不能添加其他内容,如果是空格还会报错。因此,推荐使用下面这种v-slots的写法:

jsx 复制代码
<Content
  v-slots={{
    title: () => <h2>标题</h2>,
  }}
>
  <div>内容</div>
</Content>
相关推荐
ziyue757523 分钟前
vue修改element-ui的默认的class
前端·vue.js·ui
树叶会结冰44 分钟前
HTML语义化:当网页会说话
前端·html
冰万森1 小时前
解决 React 项目初始化(npx create-react-app)速度慢的 7 个实用方案
前端·react.js·前端框架
牧羊人_myr1 小时前
Ajax 技术详解
前端
浩男孩1 小时前
🍀封装个 Button 组件,使用 vitest 来测试一下
前端
蓝银草同学1 小时前
阿里 Iconfont 项目丢失?手把手教你将已引用的 SVG 图标下载到本地
前端·icon
布列瑟农的星空1 小时前
重学React —— React事件机制 vs 浏览器事件机制
前端
程序定小飞2 小时前
基于springboot的在线商城系统设计与开发
java·数据库·vue.js·spring boot·后端
一小池勺2 小时前
CommonJS
前端·面试