> 技术文档 > 【vue vapor jsx 未雨绸缪】

【vue vapor jsx 未雨绸缪】

随着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:组件定义

// 原export default defineComponent({ name: \'xx\', setup(props, { attrs, slots, emit, expose }) { return () => <Comp /> }})// vapor jsxexport 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:编译器宏

  1. 前置条件:需要手动开启marcos
// vite.config.tsimport { defineConfig } from \'vite\'import vueJsxVapor from \'vue-jsx-vapor/vite\'export default defineConfig({ plugins: [ vueJsxVapor({ macros: true }) ]})// ts-macro.config.tsimport vueJsxVapor from \'vue-jsx-vapor/volar\'export default { plugins: [ vueJsxVapor({ macros: true }) ],}
  1. defineModel
    手动挡和自动挡的区别,vapor中如果需要触发外层事件这种,按照相关issue来看应该是不会做defineEmits宏,所以需要通过props拿到事件,如onChange?.(data),就相当于emit(‘change’, data)这种触发形式了
// 假设<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 ...})
  1. defineSlots
// 原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 />)})
  1. defineExpose
// 原export default defineComponent({ setup(props, { expose }) { expose({ a: \'xx\' }) return ... }})// vapor jsx, 与SFC使用一致export default defineComponent(() => { defineExpose({ a: \'xx\' }) return ...})
  1. defineStyle
    这个比较厉害,支持 CSS 变量和 JS 变量绑定;支持在文件中定义多个样式宏;支持多个 CSS 预处理器:cssscsssasslessstyluspostcss;在函数内部定义则scope选项默认为true;支持css-modules
defineStyle(` .red { color: red; } `)defineStyle.css({...})defineStyle.scss({...})defineStyle.less({...})defineStyle.stylus({...})// css-modulesconst styles = defineStyle.scss(` .foo { color: blue; .bar { background: red; } }`)<div class={styles.foo} />

三、变化3:内置指令

  1. v-if、v-else-if、v-else
// 原简单的条件可以用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> )})
  1. v-for
// 原<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>)
  1. v-slot、v-slots
// 原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 }} /> </> )})
  1. v-model
    babel-plugin-jsx还提供过v-models,但1.1.0版本过后不推荐使用,这里就不再提,vapor jsx也不支持v-models,这个指令两个版本使用差别不大,vapor jsx拓展了动态属性与修饰符的用法
// 原<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 // 修饰符,因为jsx不支持.关键字,所以用下划线_代替<input v-model_number={value} /> // 等同于SFC 

总结

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-slotv-slots 样式处理 传统CSS方案 defineStyle