摘要:拿到的都是而不是原始值,且這個值會動態變化。精讀對于的與,筆者做一些對比。因此采取了作為優化方案只有當第二個依賴參數變化時才返回新引用。不需要使用等進行性能優化,所有性能優化都是自動的。前端精讀幫你篩選靠譜的內容。
1. 引言
Vue 3.0 的發布引起了軒然大波,讓我們解讀下它的 function api RFC 詳細了解一下 Vue 團隊是怎么想的吧!
首先官方回答了幾個最受關注的問題:
Vue 3.0 是否有 break change,就像 Python 3 / Angular 2 一樣?
不,100% 兼容 Vue 2.0,且暫未打算廢棄任何 API(未來也不)。之前有草案試圖這么做,但由于用戶反饋太猛,被撤回了。
Vue 3.0 的設計蓋棺定論了嗎?
沒有呀,這次精讀的稿子就是 RFC(Request For Comments),翻譯成中文就是 “意見征求稿”,還在征求大家意見中哦。
這 RFC 咋這么復雜?
RFC 是寫給貢獻者/維護者的,要考慮許多邊界情況與細節,所以當然會復雜很多嘍!當然 Vue 本身使用起來還是很簡單的。
Vue 本身 Mutable + Template 就注定了是個用起來簡單(約定 + 自然),實現起來復雜(解析 + 雙綁)的框架。
這次改動很像在模仿 React,為啥不直接用 React?
首先 Template 機制還是沒變,其次模仿的是 Hooks 而不是 React 全部,如果你不喜歡這個改動,那你更不會喜歡用 React。
PS: 問這個問題的人,一定沒有同時理解 React 與 Vue,其實這兩個框架到現在差別蠻大的,后面精讀會詳細說明。
下面正式進入 Vue 3.0 Function API 的介紹。
2. 概述Vue 函數式基本 Demo:
count is {{ count }} plusOne is {{ plusOne }}
函數式風格的入口是 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: `{{ msg }}` }
要注意的是,value() 返回的是一個對象,通過 .value 才能訪問到其真實值。
為何 value() 返回的是 Wrappers 而非具體值呢?原因是 Vue 采用雙向綁定,只有對象形式訪問值才能保證訪問到的是最終值,這一點類似 React 的 useRef() API 的 .current 規則。
那既然所有 value() 返回的值都是 Wrapper,那直接給模版使用時要不要調用 .value 呢?答案是否定的,直接使用即可,模版會自動 Unwrapping:
const MyComponent = { setup() { return { count: value(0) } }, template: `` }
接下來是 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: `{{ x }} {{ y }} {{ z }}` }
可以看到,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、生命周期函數、依賴注入,都是一些語法定義,感興趣可以繼續閱讀原文,筆者就不贅述了。
3. 精讀對于 Vue 3.0 的 Function API + Hooks 與 React Function Component + Hooks,筆者做一些對比。
Vue 與 React 邏輯結構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: ` {{x}} ` }
雖然在 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 ( ) }
這源于 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 呀):
Vue:Mutable + Template
React:Immutable + JSX
真正影響編碼習慣的就是 Mutable 與 Immutable,使用 Vue 就堅定使用 Mutable,使用 React 就堅定使用 Immutable,這樣能最大程度發揮兩套框架的價值。
Vue Hooks 與 React Hooks 的差異先看 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: `{{ x }} {{ y }} {{ z }}` }
由于 setup 函數僅執行一次,怎么做到當 useMouse 導致 x、y 值變化時,可以在 setup 中拿到最新的值?
在 React 中,useMouse 如果修改了 x 的值,那么使用 useMouse 的函數就會被重新執行,以此拿到最新的 x,而在 Vue 中,將 Hooks 與 Immutable 深度結合,通過包裝 x.value,使得當 x 變更時,引用保持不變,僅值發生了變化。所以 Vue 利用 Proxy 監聽機制,可以做到 setup 函數不重新執行,但 Template 重新渲染的效果。
這就是 Immutable 的好處,Vue Hooks 中,不需要 useMemo useCallback useRef 等機制,僅需一個 value 函數,直觀的 Mutable 修改,就可以實現 React 中一套 Immutable 性能優化后的效果,這個是 Mutable 的魅力所在。
Vue Hooks 的優勢筆者對 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,導致沒有生效,隨時可能寫出依賴不正確的 “危險代碼”,造成比如死循環等嚴重后果)
4. 總結通過對比 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 的最大價值。
討論地址是:精讀《Vue3.0 Function API》 · Issue #173 · dt-fe/weekly
如果你想參與討論,請 點擊這里,每周都有新的主題,周末或周一發布。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公眾號
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105178.html
摘要:精讀前端可以從多個角度理解,比如規范框架語言社區場景以及整條研發鏈路。同是前端未來展望,不同的文章側重的格局不同,兩個標題相同的文章內容可能大相徑庭。作為使用者,現在和未來的主流可能都是微軟系,畢竟微軟在操作系統方面人才儲備和經驗積累很多。 1. 引言 前端展望的文章越來越不好寫了,隨著前端發展的深入,需要擁有非常寬廣的視野與格局才能看清前端的未來。 筆者根據自身經驗,結合下面幾篇文章...
摘要:中類型推導部分預期想實現的效果在中對應的類型是對應的類型是對應的類型是但是,我們想要實現的是轉換成小寫的所以我們寫個泛型來轉換預覽鏈接定義的類型留個泛型是給復雜類型做兼容復雜的類型定義函數接收的類型最關鍵的一步根據輸入的類型計算出來函 rfc 中類型推導部分 Type Inference 預期想實現的效果 createComponent({ props: { foo: {...
摘要:環境配置項目中的不同開發環境有很多依賴配置,所以可以根據環境設置不同的配置,以免在不同環境經常修改文件在根目錄下創建環境文件,可以在不同環境設置一些配置變量,如圖文件配置在文件里面有一個對象,可設置如圖配置在里面需在對象里面設置重點刪除默認 1.環境配置 項目中的不同開發環境有很多依賴配置,所以可以根據環境設置不同的配置,以免在不同環境經常修改文件 1 在根目錄下創建 `.env.[環...
閱讀 4375·2021-09-09 09:33
閱讀 2381·2019-08-29 17:15
閱讀 2369·2019-08-29 16:21
閱讀 971·2019-08-29 15:06
閱讀 2612·2019-08-29 13:25
閱讀 577·2019-08-29 11:32
閱讀 3246·2019-08-26 11:55
閱讀 2587·2019-08-23 18:24