這是第 59 篇不摻水的原創,想獲取更多原創好文,請掃 👆 上方二維碼關注咱們吧~ 本文首發於政採雲前端團隊博客:擁抱 Vue 3 系列之 JSX 語法html
「別再更新了,學不動了」。這句話不知道出了多少開發者的辛酸。前端
在過去的一年中,Vue 團隊一直都在開發 Vue.js 的下一個主要版本,就在 6 月底,尤大更新同步了 Vue 3 及其周邊生態的狀態(Vue 3: mid 2020 status update)。vue
if (isTrue("I am planning to use Vue 3 for a new project")) {
if (isTrue("I need IE11 support")) {
await IE11CompatBuild() // July 2020
}
if (isTrue("RFCs are too dense, I need an easy-to-read guide")) {
await migrationGuide() // July 2020
}
if (isTrue("I'd rather wait until it's really ready") {
await finalRelease() // Targeting early August 2020
})
run(`npm init vite-app hello-vue3`)
return
}
複製代碼
咱們能夠看到,若是一切順利的話,預計在 8 月份,Vue 3 的正式版本就能夠和咱們見面了,目前距離發佈正式版還有必定的差距,還要作一些兼容性的工做。同時還會提供對 IE11 的支持。git
Vue 3 爲了達到更快、更小、更易於維護、更貼近原生、對開發者更友好的目的,在不少方面進行了重構:github
這是該系列文章的第一篇,後續會持續更新,覆蓋 Vue 3生態經常使用庫。express
JSX 是一個小衆羣體使用開發方式,第一篇以 JSX 爲切入點,目標是讓大多數開發 Vue 的同窗也對 JSX 有必定的認知,在用 Vue 開發複雜應用時,也能有更加靈活的方式。npm
好比當開始寫一個只能經過 level
prop 動態生成標題 (heading) 的組件時,你可能很快想到這樣實現:編程
<script type="text/x-template" id="anchored-heading-template"></script>
複製代碼
這裏用模板並非最好的選擇,在每個級別的標題中重複書寫了 <slot></slot>
,不夠優雅。api
若是嘗試用 JSX 來寫,代碼就會變得簡單不少。babel
const App = {
render() {
const tag = `h${this.level}`
return <tag>{this.$slots.default}</tag>
}
}
複製代碼
看過 Ant Design Vue 源碼 (下面簡稱爲 antdv) 的同窗應該知道, antdv 的底層是基於 JSX 來實現的,也是 Vue 生態中使用 JSX 的深度用戶。
antd 爲了儘快的兼容 Vue 3,和 Vue 官方展開合做,因而有了 @ant-design-vue/babel-plugin-jsx。
對於使用 React 的開發者來講,JSX 再熟悉不過了,可是若是你是一個 Vue 的重度用戶,可能對 JSX 不是特別熟悉,甚至聽到有同窗說沒有 template 的 Vue 項目沒有靈魂。
先來看下面一段代碼:
const el = <div>Vue 3</div>;
複製代碼
這段代碼既不是 HTML 也不是字符串,被稱之爲 JSX,是 JavaScript 的擴展語法。JSX 可能會令人聯想到模板語法,可是它具有 Javascript 的徹底編程能力。
看到這裏可能會有疑問,很多同窗可能會覺得 JSX 是 React 中特有的,其實否則。
大多數同窗都知道,咱們日常在 .vue 文件中開發的代碼,實際上會被 vue-loader 處理,但可能少數同窗去看過咱們手把手寫出的代碼,會變編譯成啥樣。
有興趣的同窗能夠戳這個地址來看下。vue-template-explorer (由於衆所周知的緣由,可能訪問略慢)
<div id="app">{{ msg }}</div>
複製代碼
function render() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_v(_s(msg))])
}
}
複製代碼
觀察上述代碼咱們發現,到運行階段實際上都是 render 函數在執行。Vue 推薦在絕大多數狀況下使用 template 來建立你的 HTML。然而在一些場景中,就須要使用 render 函數,它比 template 更加靈活。
寫過 render 函數的同窗可能深有體會,書寫複雜的 render 函數異常痛苦,並且難以維護,很是容易被稱之爲 「祖傳代碼」。好在2.0 的官方提供了一個 Babel 插件,能夠將更接近於模板語法的 JSX 轉譯成 JavaScript。
使用過 React 的同窗對於如何寫 JSX 語法必定很是熟悉了,然而,Vue 2 中 的 JSX 寫法和 React 仍是有一些略微的區別。React 中全部傳遞的數據都掛在頂層。
const App = <A className="x" style={style} onChange={onChange} /> 複製代碼
Vue 2 中,僅僅屬性就有三種:組件屬性 props
,普通 html 屬性attrs
,DOM 屬性 domProps
。想要更多瞭解如何在 Vue 2 中寫 JSX 語法,能夠看這篇,在 Vue 中使用 JSX 的正確姿式。
Vue 3 中,屬性這塊的傳遞和 React 相似,意味這不須要再傳遞 props,attrs 這些屬性。
// before
{
class: ['foo', 'bar'],
style: { color: 'red' },
attrs: { id: 'foo' },
domProps: { innerHTML: '' },
on: { click: foo },
key: 'foo'
}
// after
{
class: ['foo', 'bar'],
style: { color: 'red' },
id: 'foo',
innerHTML: '',
onClick: foo,
key: 'foo'
}
複製代碼
Vue 3 把大多數全局 API 和 內部 helper 移到了 ES 模塊中導出(譬如 v-model、transition、teleport),從而使得 Vue 3 在增長了不少新特性以後,基線的體積反而小了。
v-model
、v-show
這些 API 所有經過模塊導出的方式來引入
基線體積: 沒法捨棄的代碼體積
咱們來看一段很是簡單的代碼 <input v-model="x" />
,在 Vue 2 和 Vue 3 中的編譯結果有何不一樣。
// before
function render() {
with(this) {
return _c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (x),
expression: "x"
}],
domProps: {
"value": (x)
},
on: {
"input": function ($event) {
if ($event.target.composing) return;
x = $event.target.value
}
}
})
}
}
複製代碼
// after
import { vModelText as _vModelText, createVNode as _createVNode, withDirectives as _withDirectives, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return _withDirectives((_openBlock(), _createBlock("input", {
"onUpdate:modelValue": $event => (_ctx.x = $event)
}, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [
[_vModelText, _ctx.x]
])
}
複製代碼
能夠看到在 Vue 3 中,對各個 API 作了更加細緻的拆分,理想狀態下,用戶能夠在構建時利用搖樹優化 (tree-shaking) 去掉框架中不須要的特性,只保留本身用到的特性。
模版編譯器會生成適合作 tree-shaking 的代碼,不須要使用者去關心如何去作,這部分的改動一樣須要在 JSX 寫法中實現。
模板編譯器中增長了 PatchFlag
,在 JSX 的編譯過程一樣也作了處理,性能會有提高,可是考慮到 JSX 的靈活性,作了一些兼容處理,該功能還在測試階段。
Vue 3 雖然引入了一部分破壞性的更新,但對於絕大多數 Vue 2 的 API 仍是兼容的。那麼一樣的,咱們也要儘量讓使用 JSX 的用戶經過最小的成本升級到 Vue 3,這是一個核心的目標。
寫這篇文章的時候,antdv 已經使用 @ant-design-vue/babel-plugin-jsx 重構了大約 70% 的功能,預計會在 Vue 3 正式版以前發佈測試版,大機率會是東半球最快兼容 Vue 3 的企業級組件庫。
const App = () => <div>Vue 3 JSX</div>
複製代碼
const App = {
render() {
return <div>Vue 3.0</div>
}
}
複製代碼
const App = defineComponent(() => {
const count = ref(0);
const inc = () => {
count.value++;
};
return () => (
<div onClick={inc}> {count.value} </div>
)
})
複製代碼
const App = () => (
<> <span>I'm</span> <span>Fragment</span> </> ) 複製代碼
Fragment 參考 React 的寫法,儘量寫起來更加方便。
const App = () => <input type="email" />
複製代碼
const placeholderText = 'email'
const App = () => (
<input type="email" placeholder={placeholderText} /> ) 複製代碼
建議在 JSX 中使用駝峯 (
vModel
),可是v-model
也能用
v-show
const App = {
data() {
return { visible: true };
},
render() {
return <input vShow={this.visible} />; }, }; 複製代碼
v-model
修飾符:使用 (
_
) 代替 (.
) (vModel_trim={this.test}
)
export default {
data: () => ({
test: 'Hello World',
}),
render() {
return (
<>
<input type="text" vModel_trim={this.test} />
{this.test}
</>
)
},
}
複製代碼
自定義指令
const App = {
directives: { antRef },
setup() {
return () => (
<a vAntRef={(ref) => { this.ref = ref; }} /> ); }, } 複製代碼
關於指令、插槽最終的 API 還在討論中,有想法的能夠去留言。Vue 3 JSX Design
因爲 antdv 的底層基本上都是基於 JSX 來寫的,想要快速遷移到 Vue 3,就必須有一個比較好的插件來支持,這也是爲何會有這個插件的緣由。固然在實現過程當中也踩了不少坑。
目前用法和 Vue 2 的語法大多數是一致的,爲了幫助更快遷移,在插件中作了針對舊 VNode 格式的兼容層,這裏只能兼容一部分寫法,以及部分語法的兼容會增長運行時的性能開銷,因此咱們但願可以將咱們的經驗分享給你們,讓你們少走彎路!
{
"plugins": ["@ant-design-vue/babel-plugin-jsx", { "transformOn": true, "compatibleProps": true }]
}
複製代碼
針對 Vue 2 中 on: { click: xx }
寫法的兼容,在運行時中會轉爲 onClick: xxx
。
上文提到 Vue 3 對屬性的傳遞作了變動,props
、attrs
這些都不存在了,所以若是設置了這個屬性爲 true
,在運行時也會被解構到第一層的屬性中。
須要注意的一點,目前一旦開啓這兩個屬性,在 createVNode
的第二個參數,都會包一個 compatibleProps
和 transformOn
方法,因此酌情開啓這兩個參數。對於使用 Vue 2 的 JSX 同窗,若是沒有使用到比較」鮮爲人知「 的 API的狀況下,均可以快速得遷移。
那麼 antdv 又是如何作遷移的呢?考慮到 antdv 是個組件庫,都包一層 compatibleProps
勢必不太優雅,所以沒有選擇開啓這個兩個開關。這裏插一句,目前 antdv 的遷移還在進行中,相關的進度都在這個 issue 裏面(Vue 3 支持),有興趣的同窗能夠關注下,提一些 PR 過去。
對於 props 的遷移工做比較簡單,只須要把原有分散在 props
、on
、attrs
中的值直接鋪開便可。
const vcUploadProps = {
- props: {
- ...this.$props,
- prefixCls,
- beforeUpload: this.reBeforeUpload,
- },
- on: {
- start: this.onStart,
- error: this.onError,
- progress: this.onProgress,
- success: this.onSuccess,
- reject: this.onReject,
- },
+ ...this.$props,
+ prefixCls,
+ beforeUpload: this.reBeforeUpload,
+ onStart: this.onStart,
+ onError: this.onError,
+ onProgress: this.onProgress,
+ onSuccess: this.onSuccess,
+ onReject: this.onReject,
+ ref: 'uploadRef',
+ attrs: this.$attrs,
+ ...this.$attrs,
};
複製代碼
可是關於 inheritAttrs
有個較爲底層的變更,須要開發者根據實際狀況去修改。什麼是inheritAttrs? 在 Vue 2 中,這個選項不影響 class
和 style
綁定,可是在 Vue 3 中會影響到。所以可能在屬性的傳遞上,須要額外對這兩個參數作處理。
在事件的處理上,咱們建議在 props 中聲明,這樣對後續的開發更加易維護,能夠很直觀地從 props 看出我這個組件到底會傳遞哪些事件。值得一提的是,在 props 中聲明的事件,也能夠經過 emit
來觸發。例如聲明瞭 onClick
事件,仍然可使用 emit('click')
。
Vue 3 對 context 的 API 也作了改動,通常若是不是複雜的組件,不會涉及到這個 API。這部分的改動能夠看原先 Vue Compositon API 的相關文檔,Dependency Injection,注意一點,在 setup 中取不到 this
。
現在有超過百萬的開發人員使用 Vue,還有超百萬的 React 開發者正在去使用 Vue 的路上。
雖說Vue 中 JSX 的開發方式是一個少數羣裏,可是 antdv 的使用用戶也不是少數。爲了讓這部分用戶能夠快速體驗到兼容 Vue 3 版本的組件庫,所以在設計這個插件的時候,第一原則就是要最小的遷移和認知成本。
對於常年使用 template 的開發者來講,JSX 又未嘗不是一片新的天空呢?開發者要與使用者共情,站在使用者的角度出發,設計出的工具大機率可能知足其需求。
距離 JSX 發佈正式版本,還有一部分路要走。
最後要感謝 Vue.js 官方團隊和 @sodatea 大佬的信任。
文中出現的倉庫地址:
擁抱 Vue 3 系列之如何開發設計一個 Vue 3 JSX 插件
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「 5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com