隨著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構建的組件庫,感覺適配的工作量會很大,對于參與開源有興趣的朋友可以關注一下
// 獲得編譯器宏和指令的類型提示
vscode 中安裝 ts-macro 插件,項目中安裝 `@ts-macro/tsc` 來替代 `tsc` 進行類型檢查// package.json
{"scripts": {"typecheck": "tsmc --noEmit"// ...}
}
一、變化1:組件定義
// 原
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 />
})
// 以及下面兩種形式
export default defineComponent(() => {// ...同上return <Comp />
})
export default () => {// ...同上return <Comp />
}
vue-jsx-vapor
支持 Virtual DOM 和 Vapor DOM 混合使用。將 interop
設置為 true
后,在 defineVaporComponent
中定義的 JSX 會被編譯為 Vapor DOM, 在 defineVaporComponent
外定義的 JSX 會被編譯為 Virtual DOM
// vite.config.ts
import { defineConfig } from 'vite'
import vueJsxVapor from 'vue-jsx-vapor/vite'export default defineConfig({plugins: [vueJsxVapor({interop: true,}),],
})
二、變化2:編譯器宏
- 前置條件:需要手動開啟marcos
// 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)這種觸發形式了
// 假設
<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 = xxreturn ...
})
- 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 />)
})
- defineExpose
// 原
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
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
老版中簡單的條件可以用v-show,稍微復雜一點的可以用三元表達式,再稍微復雜點還可以三元表達式套娃
// 是的老版也提供了一些內置指令支持
export default defineComponent({setup() {return () => (<><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
// 原
<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
// 原
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拓展了動態屬性與修飾符的用法
// 原
<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不支持數組表達式,所以用$代替,即`$name$`這種形式
<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 宏 |