随着vue3.6.0 alpha的发布,vapor mode进入正式版本只是时间上的问题,可以预见的是各个组件库都将积极适配vapor,这篇文章主要侧重vue中使用jsx而非SFC,所以不涉及template相关。目前vue官方也是提供了vue-jsx-vapor
这个仓库,处于v2.5.4-beta.1阶段,而vue3发布以来使用jsx的插件为 @vitejs/plugin-vue-jsx
,依赖的是vue官方仓库的babel-plugin-jsx
,不知道后面会不会直接用vapo jsx还是说两种jsx开发方式并行,ant-deisgn-vue仓库已经将适配vapor mode提上了日程,对于ant-design-vue这种全部由jsx构建的组件库,感觉适配的工作量会很大,对于参与开源有兴趣的朋友可以关注一下
一、变化1:组件定义
jsx
// 原
export default defineComponent({
name: 'xx',
setup(props, { attrs, slots, emit, expose }) {
return () => <Comp />
}
})
// vapor jsx
export default defineVaporComponent((props) => {
const attrs = useAttrs()
const slots = useSlots()
const model = defineModel()
defineExpose({...})
return <Comp />
})
vue-jsx-vapor
支持 Virtual DOM 和 Vapor DOM 混合使用。将 interop
设置为 true
后,在 defineVaporComponent
中定义的 JSX 会被编译为 Vapor DOM, 在 defineVaporComponent
外定义的 JSX 会被编译为 Virtual DOM
二、变化2:编译器宏
- 前置条件:需要手动开启marcos
js
// vite.config.ts
import { defineConfig } from 'vite'
import vueJsxVapor from 'vue-jsx-vapor/vite'
export default defineConfig({
plugins: [
vueJsxVapor({ macros: true })
]
})
// ts-macro.config.ts
import vueJsxVapor from 'vue-jsx-vapor/volar'
export default {
plugins: [
vueJsxVapor({ macros: true })
],
}
- defineModel
手动挡和自动挡的区别,vapor中如果需要触发外层事件这种,按照相关issue来看应该是不会做defineEmits宏,所以需要通过props拿到事件,如onChange?.(data),就相当于emit('change', data)这种触发形式了
javascript
// 假设
<comp v-model="data" />
// 原
export default defineComponent({
emits: ['update:modelValue'],
setup(props, { emit }) {
emit('update:modelValue', xx)
return ...
}
})
// vapor jsx, 与SFC使用一致
export default defineComponent(() => {
const model = defineModel()
model.value = xx
return ...
})
- defineSlots
typescript
// 原
export default defineComponent({
slots: Object as SlotsType<{
default: any
}>,
setup(props, { slots }) {
return () => (<div>{ slots.default?.() } </div>)
}
})
// vapor jsx,与SFC使用有一点差异
export default defineComponent(() => {
const slots = defineSlots({
default: () => <div>default</div>
})
return (<slots.default />)
})
- defineExpose
javascript
// 原
export default defineComponent({
setup(props, { expose }) {
expose({
a: 'xx'
})
return ...
}
})
// vapor jsx, 与SFC使用一致
export default defineComponent(() => {
defineExpose({
a: 'xx'
})
return ...
})
- defineStyle
这个比较厉害,支持 CSS 变量和 JS 变量绑定;支持在文件中定义多个样式宏;支持多个 CSS 预处理器:css
、scss
、sass
、less
、stylus
、postcss
;在函数内部定义则scope选项默认为true;支持css-modules
less
defineStyle(`
.red {
color: red;
}
`)
defineStyle.css({...})
defineStyle.scss({...})
defineStyle.less({...})
defineStyle.stylus({...})
// css-modules
const styles = defineStyle.scss(`
.foo {
color: blue;
.bar {
background: red;
}
}
`)
<div class={styles.foo} />
三、变化3:内置指令
- v-if、v-else-if、v-else
javascript
// 原
简单的条件可以用v-show,是的老版也提供了一些内置指令支持 <div v-show={isVisible} />
稍微复杂一点的可以用三元表达式 {isStatus ? <CompA /> : <CompB />},再稍微复杂点可以三元表达式套娃
// vapor jsx,使用与SFC基本一致
export default defineComponent(({ count = 0! }) => {
return (
<fieldset>
<legend>If</legend>
<div v-if={count === 0}>eq {count}</div>
<div v-else-if={count > 0}>lg {count}</div>
<div v-else>lt {count}</div>
</fieldset>
)
})
- v-for
javascript
// 原
<div>
{{
data.map((item, index) => {
return (<div key={index}>{item}</div>)
})
}}
</div>
// vapor jsx,与SFC使用基本一致
export default () => (
<div v-for={(item, index) in 4} key={index}>
{item}
</div>
)
- v-slot、v-slots
jsx
// 原
export default defineComponent({
slots: Object as SlotsType<{
default: any
}>,
setup(props, { slots }) {
// 取插槽传递的值
return () => (<Child v-slots={{ default: (data) => <div>{data}</div> }>)
/ 向子组件传递插槽
return () => (<Child v-slots={slots}>)
// 或者
return () => (<Child v-slots={{ default: slots.default }>)
// 或者
return () => (<Child>{ slots.default?.() }</Child>)
// 亦或者
return () => (
<Child>
<Comp />
</Child>
)
}
})
// vapor jsx,新增了v-slot,
export default defineComponent(({ count = 0! }) => {
return (
<>
// 取插槽传递出的值
<ChildA v-slot={{ foo }}>
{{ foo }}
</ChildA>
// 向子组件传递插槽
<ChildB v-slots={{ title: xx }} />
</>
)
})
- v-model
babel-plugin-jsx还提供过v-models,但1.1.0版本过后不推荐使用,这里就不再提,vapor jsx也不支持v-models,这个指令两个版本使用差别不大,vapor jsx拓展了动态属性与修饰符的用法
ini
// 原
<input v-model={val} />
<input v-model:argument={val} />
<input v-model={[val, ['modifier']]} />
// 或者
<input v-model_modifier={val} />
<A v-model={[val, 'argument', ['modifier']]} />
// 或者
<input v-model:argument_modifier={val} />
// vapor jsx
<imput v-model={val} />
<input v-model:argument={val} />
// 动态参数,因为jsx不支持数组表达式,所以用$代替
<input v-model:$name={foo} /> // 等同于SFC <input v-model['name']="foo" />
// 修饰符,因为jsx不支持.关键字,所以用下划线_代替
<input v-model_number={value} /> // 等同于SFC <input v-model.number="value" />
总结
vue之前的jsx的整体结构更偏向于options API,虽然在setup中用的都是composition API,这种算是承接了vue2到vue3的转变,开发体验也比较向react jsx的方向靠拢,新的vapor jsx则是减少了与 SFC 使用的割裂感,将编译器宏、内置指令也带到了jsx开发中,降低了上手难度,也许会吸引到更多的用户来体验在vue中使用jsx开发
特性 | JSX (babel-plugin-jsx ) |
Vapor JSX (vue-jsx-vapor ) |
---|---|---|
组件定义 | setup() 返回渲染函数 |
直接返回JSX |
编译器宏 | 不支持 | defineXxx 系列 |
指令支持 | 仅基础指令(v-show) | 常用指令(v-if/v-for等) |
插槽系统 | v-slots |
v-slot 、v-slots |
样式处理 | 传统CSS方案 | defineStyle 宏 |