前言
写这篇文章的初衷是,Vue3 JSX 部分与 React JSX 容易混淆,比如如本文所说的 slot & v-model,
如果你是第一次接触 JSX,可先阅读前面写过的 React & JSX 日常用法与基本原则 来对 JSX 有一个整体的认知以及比较两者间的差异。
一、什么是 JSX
JSX
是用一种可以将 HTML + JS 混合写的一种语法糖,最初由 React
率先研发,后来 Vue3
也借鉴了这种写法灵感实现自家的 JSX
。
实际上,能够快速研发 JSX
还得得益于一款叫做由 @babel/plugin-syntax-jsx
的插件,
是由 Babel
研发的,它只负责解析但不负责转换,于是 React
和 Vue3
基于此插件实现了自家的 JSX
。
举个例子
const Component = () => <div> {[1,2,3].map((num) => <span>{num}</span>)} </div>
// 等价于 <div><span>1</span><span>2</span><span>2</span></div>
这种写法就叫 JSX
,换句话说,只要你用过 React/Vue3 其中的 JSX
,你就同时掌握了两种框架的 JSX
。当然,每个框架对自己的 JSX
都喜欢添砖加瓦,如独特的 API,独特的属性修饰符;
但万变不离其宗, HTML + JS 写法是任何 JSX 框架统一遵循规范的不变事实。
二、Vu3 使用 JSX
2.1 准备工作
a) 针对新项目
使用官方提供的安装命令,里面内置了 JSX、TypeScript 等插件,无须手动配置
npm init vue@latest
按照提示选项勾选即可,
接下来就可以愉快的使用 JSX 和 TypeScript ~
b) 针对现有项目
- 安装 JSX 依赖包:
npm install @vue/babel-plugin-jsx -D
- 新建 Babel 配置文件:
babel.config.json
,内容如下:
{
"plugins": ["@vue/babel-plugin-jsx"]
}
然后就可以愉快的使用 JSX 啦~
对有关 Babel 的概念感兴趣的可参考前面写过的:
JS & 介绍 Babel 的使用及 presets & plugins 的概念
2.2 使用 slot 插槽
定义插槽方式1
// NavBar.jsx
import { defineComponent } from 'vue'
const NavBar = defineComponent({
setup(props, { slots }) {
return () => {
return (
<>
<div>Simple</div>
<div> { slots?.default() } </div>
<header> { slots?.header() } </header>
</>
)
}
}
})
注意,使用 slot 时,setup return 必须是一个函数,实际上就是 render() 。
定义插槽方式2
// NavBar.jsx
const NavBar = (props, { slots }) => {
return (
<>
<div>Simple</div>
<div> { slots?.default() } </div>
<header> { slots.header() } </header>
</>
)
}
这两种定义方式的区别取决于你是否需要用到 Vue 提供的一些钩子函数或响应式 API,比如第一种有 setup ,如果是纯普素的组件,使用第二种方式即可。
使用插槽方式1:双括号
// App.jsx
import NavBar from '@/components/nav-bar'
const App = () => {
return (
<>
<NavBar>
{{
default: () => 'Hello,world.',
header: () => 'Hello, header.'
}}
</NavBar>
</>
)
}
export default App
使用插槽方式2:v-slots
// App.jsx
import NavBar from '@/components/nav-bar'
const App = () => {
const slots = {
default: () => 'Hello,world.',
header: () => 'Hello, header.'
}
return (
<>
<NavBar v-slots={slots}>
</NavBar>
</>
)
}
export default App
使用插槽方式3:把它当成 .vue 文件使用即可
// App.vue
<template>
<div>
<NavBar>
Hello, world.
<template #header>
Hello, header.
</template>
</NavBar>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import NavBar from '@/components/nav-bar'
export default defineComponent({
components: {
NavBar
},
props: {},
setup(props) {
return {}
}
})
</script>
提示:
.vue
组件可以引入.jsx/tsx
组件,反之.jsx/tsx
也可以引入.vue
组件,它们本质上就是一个 Object 。
2.3 使用 v-model
使用方式1:标签绑定 v-model
// NavBar.jsx
import { ref, defineComponent } from 'vue'
const NavBar = defineComponent({
setup() {
const phone = ref()
return () => {
return (
<input type="text" v-model={phone.value} />
)
}
}
})
export default NavBar;
使用方式2:组件绑定 v-model
// Card.jsx 引入 component 组件。
import { defineComponent, ref } from 'vue'
import Component from './component'
const CardComponent = defineComponent({
setup() {
const componentName = ref()
return () => {
return (
<main>
<Component
v-model={[componentName.value, 'modelValue']}
/>
</main>
)
}
}
})
export default CardComponent
// 定义组件 Component.jsx 。
import { defineComponent, toRefs } from 'vue'
const Component = defineComponent({
emits: ['update:modelValue'],
setup(props, { emit }) {
return () => {
return (
<input type="text" onInput={($event) => emit('update:modelValue', $event.target.value)} />
)
}
}
})
export default Component
解释:
- Card.jsx 引入 Component 组件并绑定 v-model
- v-model 的第二个参数
modelValue
与 Component 组件的update:modelValue
成对应关系,是双向更新的必备定义条件。
使用方式3:组件绑定多个 v-model
// Card.jsx 引入 component 组件。
import { defineComponent, ref } from 'vue'
import Component from './component'
const CardComponent = defineComponent({
setup() {
const componentName = ref()
const modelPhoneNumber = ref()
return () => {
return (
<main>
<Component
v-model={[componentName.value, 'modelValue']}
v-model={[modelPhoneNumber.value, 'modelPhoneNumber']}
// 上面等同于下面。
// v-model:modelValue={modelValue.value}
// v-model:modelPhoneNumber={modelPhoneNumber.value}
/>
</main>
)
}
}
})
export default CardComponent
// 定义组件 Component.jsx 。
import { defineComponent, toRefs } from 'vue'
const Component = defineComponent({
emits: ['update:modelValue', 'update:modelPhoneNumber'],
setup(props, { emit }) {
return () => {
return (
<input type="text" onInput={($event) => emit('update:modelValue', $event.target.value)} />
<input type="text" onInput={($event) => emit('update:modelPhoneNumber', $event.target.value)} />
)
}
}
})
export default Component
提示:对于绑定 v-model 的组件来说,组件内无需再自定义
props: {}
,可别跟平时的传参给混淆了,如果组件内部确实要访问,则给组件加上 x={x} 然后用 props 接受即可。
2.4 支持 TSX 写法
TSX
指的是能够在 TypeScript 中写 JSX,在 tsconfig.json
中加以下选项:
"compilerOptions": {
"jsx": "preserve"
}
接下来只需将文件后缀由 .jsx
改成 .tsx
即可。
2.5 注意事项
- 使用 JSX 语法时,文件名必须为
.jsx
或.tsx
,不支持.vue
。 - 在 JSX 里的标签内访问响应式数据时,须带上
.value
访问。
三、Vue3 JSX 与 React JSX 的差别
- 绑定数据方式不同,Vue3 JSX 采用
v-model
双向绑定数据,React 采用 setState 。 - 生命周期函数不同。
- 插槽用法不同。
- 有各自的 API。
其实不管是 Vue3 or React,它们最大的特点就是 JSX ,本质上就是 HTML & JS 可以混合写,这才是它们的共同之处。
文献:
https://juju.one/using-jsx-with-vue3/
https://github.com/vuejs/babel-plugin-jsx