Vue 3.0 的發佈引發了軒然大波,讓咱們解讀下它的 function api RFC 詳細瞭解一下 Vue 團隊是怎麼想的吧!前端
首先官方回答了幾個最受關注的問題:vue
Vue 3.0 是否有 break change,就像 Python 3 / Angular 2 同樣?react
不,100% 兼容 Vue 2.0,且暫未打算廢棄任何 API(將來也不)。以前有草案試圖這麼作,但因爲用戶反饋太猛,被撤回了。git
Vue 3.0 的設計蓋棺定論了嗎?github
沒有呀,此次精讀的稿子就是 RFC(Request For Comments),翻譯成中文就是 「意見徵求稿」,還在徵求你們意見中哦。npm
這 RFC 咋這麼複雜?api
RFC 是寫給貢獻者/維護者的,要考慮許多邊界狀況與細節,因此固然會複雜不少嘍!固然 Vue 自己使用起來仍是很簡單的。性能優化
Vue 自己 Mutable + Template 就註定了是個用起來簡單(約定 + 天然),實現起來複雜(解析 + 雙綁)的框架。微信
此次改動很像在模仿 React,爲啥不直接用 React?閉包
首先 Template 機制仍是沒變,其次模仿的是 Hooks 而不是 React 所有,若是你不喜歡這個改動,那你更不會喜歡用 React。
PS: 問這個問題的人,必定沒有同時理解 React 與 Vue,其實這兩個框架到如今差異蠻大的,後面精讀會詳細說明。
下面正式進入 Vue 3.0 Function API 的介紹。
Vue 函數式基本 Demo:
<template>
<div>
<span>count is {{ count }}</span>
<span>plusOne is {{ plusOne }}</span>
<button @click="increment">count++</button>
</div>
</template>
<script>
import { value, computed, watch, onMounted } from 'vue'
export default {
setup() {
// reactive state
const count = value(0)
// computed state
const plusOne = computed(() => count.value + 1)
// method
const increment = () => { count.value++ }
// watch
watch(() => count.value * 2, val => {
console.log(`count * 2 is ${val}`)
})
// lifecycle
onMounted(() => {
console.log(`mounted`)
})
// expose bindings on render context
return {
count,
plusOne,
increment
}
}
}
</script>
複製代碼
函數式風格的入口是 setup
函數,採用了函數式風格後能夠享受以下好處:類型自動推導、減小打包體積。
setup
函數返回值就是注入到頁面模版的變量。咱們也能夠返回一個函數,經過使用 value
這個 API 產生屬性並修改:
import { value } from 'vue'
const MyComponent = {
setup(props) {
const msg = value('hello')
const appendName = () => {
msg.value = `hello ${props.name}`
}
return {
msg,
appendName
}
},
template: `<div @click="appendName">{{ msg }}</div>`
}
複製代碼
要注意的是,value()
返回的是一個對象,經過 .value
才能訪問到其真實值。
爲什麼 value()
返回的是 Wrappers 而非具體值呢?緣由是 Vue 採用雙向綁定,只有對象形式訪問值才能保證訪問到的是最終值,這一點相似 React 的 useRef()
API 的 .current
規則。
那既然全部 value()
返回的值都是 Wrapper,那直接給模版使用時要不要調用 .value
呢?答案是否認的,直接使用便可,模版會自動 Unwrapping
:
const MyComponent = {
setup() {
return {
count: value(0)
}
},
template: `<button @click="count++">{{ count }}</button>`
}
複製代碼
接下來是 Hooks,下面是一個使用 Hooks 實現得到鼠標實時位置的例子:
function useMouse() {
const x = value(0)
const y = value(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
// in consuming component
const Component = {
setup() {
const { x, y } = useMouse()
const { z } = useOtherLogic()
return { x, y, z }
},
template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}
複製代碼
能夠看到,useMouse
將全部與 「處理鼠標位置」 相關的邏輯都封裝了進去,乍一看與 React Hooks 很像,可是有兩個區別:
useMouse
函數內改變 x
、y
後,不會從新觸發 setup
執行。x
y
拿到的都是 Wrapper 而不是原始值,且這個值會動態變化。另外一個重要 API 就是 watch
,它的做用相似 React Hooks 的 useEffect,但實現原理和調用時機其實徹底不同。
watch
的目的是監聽某些變量變化後執行邏輯,好比當 id
變化後從新取數:
const MyComponent = {
props: {
id: Number
},
setup(props) {
const data = value(null)
watch(() => props.id, async (id) => {
data.value = await fetchData(id)
})
}
}
複製代碼
之因此要 watch
,由於在 Vue 中,setup
函數僅執行一次,因此不像 React Function Component,每次組件 props
變化都會從新執行,所以不管是在變量、props
變化時若是想作一些事情,都須要包裹在 watch
中。
後面還有 unwatching
、生命週期函數、依賴注入,都是一些語法定義,感興趣能夠繼續閱讀原文,筆者就不贅述了。
對於 Vue 3.0 的 Function API + Hooks 與 React Function Component + Hooks,筆者作一些對比。
React Function Component 與 Hooks,雖然在實現原理上,與 Vue3.0 存在 Immutable 與 Mutable、JSX 與 Template 的區別,但邏輯理解上有着相通之處。
const MyComponent = {
setup(props) {
const x = value(0)
const setXRandom = () => {
x.value = Math.random()
}
return { x, setXRandom }
},
template: ` <button @onClick="setXRandom"/>{{x}}</button> `
}
複製代碼
雖然在 Vue 中,setup
函數僅執行一次,看上去與 React 函數徹底不同(React 函數每次都執行),但其實 Vue 將渲染層(Template)與數據層(setup)分開了,而 React 合在了一塊兒。
咱們能夠利用 React Hooks 將數據層與渲染層徹底隔離:
// 相似 vue 的 setup 函數
function useMyComponentSetup(props) {
const [x, setX] = useState(0)
const setXRandom = useCallback(() => {
setX(Math.random())
}, [setX])
return { x, setXRandom }
}
// 相似 vue 的 template 函數
function MyComponent(props: { name: String }) {
const { x, setXRandom } = useMyComponentSetup(props)
return (
<button onClick={setXRandom}>{x}</button>
)
}
複製代碼
這源於 JSX 與 Template 的根本區別。JSX 使模版與 JS 能夠寫在一塊兒,所以數據層與渲染層能夠耦合在一塊兒寫(也能夠拆分),但 Vue 採起的 Template 思路使數據層強制分離了,這也使代碼分層更清晰了。
而實際上 Vue3.0 的 setup
函數也是可選的,再配合其支持的 TSX 功能,與 React 真的只有 Mutable 的區別了:
// 這是個 Vue 組件
const MyComponent = createComponent((props: { msg: string }) => {
return () => h('div', props.msg)
})
複製代碼
咱們很難評價 Template 與 JSX 的好壞,但爲了更透徹的理解 Vue 與 React,須要拋開 JSX&Template,Mutable&Immutable 去看,其實去掉這兩個框架無關的技術選型,React@16 與 Vue@3 已經很是像了。
Vue3.0 的精髓是學習了 React Hooks 概念,所以正好能夠用 Hooks 在 React 中模擬 Vue 的 setup 函數。
關於這兩套技術選型,已是相對完美的組合,不建議在 JSX 中再實現相似 Mutable + JSX 的花樣來(由於喜歡 Mutable 能夠用 Vue 呀):
真正影響編碼習慣的就是 Mutable 與 Immutable,使用 Vue 就堅決使用 Mutable,使用 React 就堅決使用 Immutable,這樣能最大程度發揮兩套框架的價值。
先看 React Hooks 的簡單語法:
const [ count, setCount ] = useState(0)
const setToOne = () => setCount(1)
複製代碼
Vue Hooks 的簡單語法:
const count = value(0)
const setToOne = () => count.value = 1
複製代碼
之因此 React 返回的 count
是一個數字,是由於 Immutable 規則,而 Vue 返回的 count
是個對象,擁有 count.value
屬性,也是由於 Vue Mutable 規則致使,這使得 Vue 定義的全部變量都相似 React 中 useRef
定義變量,所以不存 React capture value
的特性。
關於 capture value 更多信息,能夠閱讀 精讀《Function VS Class 組件》 Capute Value 介紹
另外,對於 Hooks 的值變動機制也不一樣,咱們看 Vue 的代碼:
const Component = {
setup() {
const { x, y } = useMouse()
const { z } = useOtherLogic()
return { x, y, z }
},
template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}
複製代碼
因爲 setup
函數僅執行一次,怎麼作到當 useMouse
致使 x
、y
值變化時,能夠在 setup
中拿到最新的值?
在 React 中,useMouse
若是修改了 x
的值,那麼使用 useMouse
的函數就會被從新執行,以此拿到最新的 x
,而在 Vue 中,將 Hooks 與 Immutable 深度結合,經過包裝 x.value
,使得當 x
變動時,引用保持不變,僅值發生了變化。因此 Vue 利用 Proxy 監聽機制,能夠作到 setup
函數不從新執行,但 Template 從新渲染的效果。
這就是 Mmutable 的好處,Vue Hooks 中,不須要 useMemo
useCallback
useRef
等機制,僅需一個 value
函數,直觀的 Mutable 修改,就能夠實現 React 中一套 Immutable 性能優化後的效果,這個是 Mutable 的魅力所在。
筆者對 RFC 中對 Vue、React Hooks 的對比作一個延展解釋:
首先最大的不一樣:setup
僅執行一遍,而 React Function Component 每次渲染都會執行。
Vue 的代碼使用更符合 JS 直覺。
這句話直截了當戳中了 JS 軟肋,JS 並不是是針對 Immutable 設計的語言,因此 Mutable 寫法很是天然,而 Immutable 的寫法就比較彆扭。
當 Hooks 要更新值時,Vue 只要用等於號賦值便可,而 React Hooks 須要調用賦值函數,當對象類型複雜時,還需藉助第三方庫才能保證進行了正確的 Immutable 更新。
對 Hooks 使用順序無要求,並且能夠放在條件語句裏。
對 React Hooks 而言,調用必須放在最前面,並且不能被包含在條件語句裏,這是由於 React Hooks 採用下標方式尋找狀態,一旦位置不對或者 Hooks 放在了條件中,就沒法正確找到對應位置的值。
而 Vue Function API 中的 Hooks 能夠放在任意位置、任意命名、被條件語句任意包裹的,由於其並不會觸發 setup
的更新,只在須要的時候更新本身的引用值便可,而 Template 的重渲染則徹底繼承 Vue 2.0 的依賴收集機制,它無論值來自哪裏,只要用到的值變了,就能夠從新渲染了。
不會再每次渲染重複調用,減小 GC 壓力。
這確實是 React Hooks 的一個問題,全部 Hooks 都在渲染閉包中執行,每次重渲染都有必定性能壓力,並且頻繁的渲染會帶來許多閉包,雖然能夠依賴 GC 機制回收,但會給 GC 帶來不小的壓力。
而 Vue Hooks 只有一個引用,因此存儲的內容就很是精簡,也就是佔用內存小,並且當值變化時,也不會從新觸發 setup
的執行,因此確實不會形成 GC 壓力。
必需要總包裹 useCallback
函數確保不讓子元素頻繁重渲染。
React Hooks 有一個問題,就是徹底依賴 Immutable 屬性。而在 Function Component 內部建立函數時,每次都會建立一個全新的對象,這個對象若是傳給子組件,必然致使子組件沒法作性能優化。 所以 React 採起了 useCallback
做爲優化方案:
const fn = useCallback(() => /* .. */, [])
複製代碼
只有當第二個依賴參數變化時才返回新引用。但第二個依賴參數須要 lint 工具確保依賴老是正確的(關於爲什麼要對依賴誠實,感興趣能夠移步 精讀《Function Component 入門》 - 永遠對依賴誠實)。
回到 Vue 3.0,因爲 setup
僅執行一次,所以函數自己只會建立一次,不存在多實例問題,不須要 useCallback
的概念,更不須要使用 lint 插件 保證依賴書寫正確,這對開發者是實實在在的友好。
不須要使用 useEffect
useMemo
等進行性能優化,全部性能優化都是自動的。
這也是實在話,畢竟 Mutable + 依賴自動收集就能夠作到最小粒度的精確更新,根本不會觸發沒必要要的 Rerender,所以 useMemo
這個概念也不須要了。
而 useEffect
也須要傳遞第二個參數 「依賴項」,在 Vue 中根本不須要傳遞 「依賴項」,因此也不會存在用戶不當心傳錯的問題,更不須要像 React 寫一個 lint 插件保證依賴的正確性。(這也是筆者想對 React Hooks 吐槽的點,React 團隊如何保障每一個人都安裝了 lint?就算裝了 lint,若是 IDE 有 BUG,致使沒有生效,隨時可能寫出依賴不正確的 「危險代碼」,形成好比死循環等嚴重後果)
經過對比 Vue Hooks 與 React Hooks 能夠發現,Vue 3.0 將 Mutable 特性完美與 Hooks 結合,規避了一些 React Hooks 的硬傷。因此咱們能夠說 Vue 借鑑了 React Hooks 的思想,但創造出來的確實一個更精美的藝術品。
但 React Hooks 遵循的 Immutable 也有好的一面,就是每次渲染中狀態被穩定的固化下來了,不用擔憂狀態忽然變動帶來的影響(其實反而要注意狀態用不變動帶來的影響),對於數據記錄、程序運行的穩定性都有較高的可預期性。
最後,對於喜歡 Mutable 的開發者,Vue 3.0 是你的最佳選擇,基於 React + Mutable 搞的一些小輪子作到頂級可能還不如 Vue 3.0。對於 React 開發者來講,堅持大家的 Immutable 信仰吧,Vue 3.0 已經將 Mutable 發揮到極致,只有將 React Immutable 特性發揮到極致才能發揮 React 的最大價值。
若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公衆號
版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證)