背景
事實上我已經有幾年沒有使用 Vue
了,尤其是在 react
釋出 hooks
之後,我已經全面轉入了 functional component
開發,好處是顯而易見的,不用再去頭疼 this
new
之類的一系列問題了,也不用再去背下一個個不同又冗長的生命週期(我已經差不多忘了他們),我一直認為這樣的開發才是最自然最貼近 js
的狀態,只要掌握 js
即可,其他多餘的並不關心。當然了,麻煩也自然不少,尤其是 hooks
設計上帶來的一些新的問題(比如閉包),對於 js
老手而言自然不是問題,但是對於新手或者對於 hooks
不熟練的人,這就非常容易踩坑了。直到最近發現 vue3 似乎釋出了第一個正式版,Vue
官網的文件也正式更新了 3.0,早就有聽說 Vue
在 3.0 也已經全面支援了 functional component
,以及相對於 hooks
的「改進版」,所以這次就來體驗體驗。
實踐
首先依舊是 Vue cli
建立一個專案,我使用的是 vue ui
,我一直覺得 vue
在這方面乾的不錯,vue ui
使用起來很方便。建立一個專案之後,我發現似乎並不能原生支援 tsx
,查詢了一下,需要安裝@vue/babel-plugin-jsx
這個 babel
外掛,安裝之後就可以直接引入 tsx
檔案了,就像這樣。
code

這裡需要注意,Vue
取消了暴露 property
,這也就意味著以前很多庫和程式碼都無法正常使用,vue3
雖然大多數語法上都相容了 vue2
,雖然我很支援取消對於 property
的支援,但是類似於這種破壞性更新也意味著最起碼短時間內 vue3
別期望能有太多庫支援了。我看了一下之前我用的 element ui
,果然不支援了…
接著看一下 tsx
,我試了一下基本和 react
沒有太大區別
export const App = () => {
return (
<div style={{ color: “red” }}>
<Hello />
</div>
)
}
function Hello() {
return <div>hello</div>
}
沒錯這就是我想要的效果,像寫 js
一樣寫 UI
,接著嘗試一下更新的新 api
,因為目前關於 vue3 jsx
寫法文章並不多,以為 relative
應該等同於 react
的 useState
,所以按照寫 react hooks
的方式嘗試寫了一下。
坑
export const App = () => {
const count = reactive({ value: 1 })
return (
<div style={{ color: “red” }}>
<button
onClick={() => {
count.value += 1
}}>
+1
</button>
{count.value}
<Hello />
</div>
)
}
這個時候發現頁面雖然能夠渲染出正確的值,但是點選之後 count
永遠都是 1,難道每次渲染 relative
都會重新生成值? 這怎麼和預想的不一樣。隨後查詢資料發現原來要這麼寫
export const App = defineComponent(() => {
const count = reactive({ value: 1 })
return () => (
<div style={{ color: “red” }}>
<button
onClick={() => {
count.value += 1
}}>
+1
</button>
{count.value}
</div>
)
})
沒錯,需要用 defineComponent
作為高階元件套一層,並且返回的需要是一個函式,而不是 react
那樣可以直接返回一個 dom
。老實說這就很奇怪了,為什麼要套個 defineComponent
,他做了什麼?查詢了一下原始碼,原來 defineComponent
並沒有實際意義,只是為了 Typescript
的型別推導。那麼如果目前我們不需要型別推導的話,大概這麼寫也是可以的
const count = reactive({ value: 1 })
export const App = () => {
return (
<div style={{ color: “red” }}>
<button
onClick={() => {
count.value += 1
}}>
+1
</button>
{count.value}
</div>
)
}
嘗試了一下,果然可以,並且也不會出現每次重新整理的時候 reactive
都重新生成一次的問題,思考了一下,這裡的 jsx
檔案應該等同於 vue
模板裡的 setup
,setup
只生成一次,後續只重新渲染 setup
返回的函式(App),而不會重新渲染整個 setup(jsx檔案),所以我們只需要返回一個渲染 dom
,relative
應該定義在渲染的函式外部。
多個元件傳參則和 react
一樣,但是需要注意,這裡並沒有 react
的 children
,依舊需要用第二個引數的 slots
來渲染子節點,這就讓我感覺很奇怪了,既然用 jsx
,就算為了相容模板需要 slots
,那也應該做一下 children
相容才對,這並不是一個很困難的事情,只需要把 children
指向 slots.default
即可,不知道以後的版本會不會做這個處理。
export const App = () => {
return (
<div style={{ color: “red” }}>
<button
onClick={() => {
count.value += 1
}}>
+1
</button>
<CountValue countValue={count.value}>你好</CountValue>
</div>
)
}
function CountValue(
{ countValue }: { countValue: number },
{ slots }: { slots: any },
) {
return (
<div>
{slots.default()}
{countValue}
</div>
)
}
demo
同時我發現這樣完全可以實現 Typescript
的型別推導
ts 型別推導

這就讓我很奇怪,為什麼明明可以實現型別推倒還需要加個 defineComponent
呢,隨後我去查了一下,原來 defineComponent
是這麼寫使用的
code
code

這個作者給出的寫法據說是 Vue
作者給出的推薦寫法,但是我並不太理解為什麼需要這麼寫,明明直接使用 jsx
函式就已經很舒服了,我個人推測 defineComponent
更多還是為了 vue
模板寫法或者這種物件的寫法而搞出的東西,如果純使用 jsx
的 functional component
寫法,應該是不需要這個的。
接著我突然想到,如果是這麼寫,其實子元件父元件完全可有其他狀態傳遞方式,比如:
const count = reactive({ value: 1 })
export const App = () => {
return (
<div style={{ color: “red” }}>
<Button />
<CountValue countValue={count.value}>你好</CountValue>
</div>
)
}
function CountValue(
{ countValue }: { countValue: number },
{ slots }: { slots: any },
) {
return (
<div>
{slots.default()}
{countValue}
</div>
)
}
function Button() {
return (
<button
onClick={() => {
count.value += 1
}}>
+1
</button>
)
}
export const App = () => {
return (
<div style={{ color: “red” }}>
<Button />
<CountValue />
</div>
)
}
function CountValue() {
return (
<div>
{count.value}
</div>
)
}
function Button() {
return (
<button
onClick={() => {
count.value += 1
}}>
+1
</button>
)
}
疑惑
並且因為 Vue
的響應式原理,父元件在使用了 count
併發生變化的時候,並不會連帶著子元件 Button 也發生變化,Button
是不會重複 Render
的,但是每次點選卻能拿到最新的值,每次點選的時候只有 CountValue
這種實際渲染了值的元件在發生 Render,這意味著實際上 Vue
哪怕是用 jsx
寫法,也原生自帶了 shouldComponentUpdate
的最佳化?
這是否意味著,其實 relative
自帶一個全域性狀態?思考了一下,從 js 角度來講,確實是這樣,目前尚不得知這樣的寫法是否會有什麼坑,是否被官方推薦,畢竟目前的資料實在太少,可能還需要後續實踐這次就先寫到這吧。
本作品採用《CC 協議》,轉載必須註明作者和本文連結