揭秘 Vue-3.0 最具潛力的 API

尤雨溪發(fā)布了 Vue Function-based API RFC硝岗,說是 3.0 最重要的 RFC渴语。

文章發(fā)布后娇未,引起了許多人的討論和爭執(zhí)。

有人表示喜歡和贊賞,有人卻表示“這不就是抄 React 嗎菱父?我干嘛不直接學(xué)React去了”。

從個人角度剑逃,相比 vue 之前的 class-component 提案浙宜,我更欣賞現(xiàn)在的 function-based 模型。表面上看它好像是 react-hooks 的翻版蛹磺。其實它跟 react-hooks 走的函數(shù)增強(qiáng)路線不同粟瞬,vue-hooks 是一個 value 增強(qiáng)的路線。

function 強(qiáng)化跟 value 強(qiáng)化萤捆,是一個能力相當(dāng)?shù)膶ε寄P腿蛊贰R粋€是 a -> data ,另一個則是 data -> a俗或。后者也是現(xiàn)在函數(shù)式研究的一個方向市怎,叫 codata(點擊 codata in action 了解更多)。

react 路線:如何從普通的 value 中辛慰,通過函數(shù)管道焰轻,輸出一個 view。

vue 路線:如何從一個特殊的(響應(yīng)式的)值中昆雀,衍生出普通的值以及 view辱志。

今天我們要揭示的蝠筑,不是上面那個最重要的,而是最具潛力的揩懒、最能表征 Vue 路線的 API什乙。

總所周知,Vue 當(dāng)年的核心競爭力之一就是從使用 ES5 的 Object.defineProperty 的 getter/setter 改良了當(dāng)時的 MVVM 使用藏檢查或者 get()/set() 函數(shù)已球。如今基于 ES2015 Proxy 升級成了新的 reactivity api臣镣。

它就是 Advanced Reactivity API,Provide standalone APIs for creating and observing reactive state智亮。(注:請先大概看一下 API 介紹忆某,它是理解后續(xù)內(nèi)容的基礎(chǔ))。

某種意義上阔蛉,vue 暴露的內(nèi)部 api(reactivity api)比 react 暴露的內(nèi)部 api(hooks)弃舒,具有更強(qiáng)的表達(dá)能力和普適性。它比 react 更完整状原,因為 value既可以衍生出state也可以衍生出 view$聋呢,它自帶了狀態(tài)管理和視圖,且兩者是無縫對接的颠区。

react hooks 只能借鑒思路削锰。在別的地方使用時,要去重新實現(xiàn)毕莱,是一種模式器贩。而 reactvity api 可以直接作為 library 來用。比如朋截,擁有了這個 API磨澡,我們可以實現(xiàn)出類似 cyclejs, rxjs, immer, react-hooks 的特性。

那么問題來了质和,vue 3.0 還沒有發(fā)布稳摄,我們沒有代碼,怎么演示和證明 reactivity api 可以作為 library 來用呢饲宿?

哼哼厦酬,這個難不倒我們。

我們來親自手?jǐn)]一個簡單的 vue 3.0 reactivity api瘫想,不就行了嗎仗阅?

具體如何實現(xiàn),不是這篇文章的重點国夜,按下不表减噪。如果你等不急看代碼和效果,可以點擊這里訪問DEMO(我基于 reactivity-api 實現(xiàn)了 counter 和 todo-list 效果)。你會發(fā)現(xiàn) reactivity.js 已經(jīng)被編譯和壓縮過了筹裕,可讀性很低醋闭。這是因為,最近前端社區(qū)有一些不良風(fēng)氣朝卒,一些小朋友证逻,從各處抄了一點代碼,就覺得實現(xiàn)了 vue/react 的核心抗斤。過分自信的在四處發(fā)表錯漏百出囚企、富有偏見的觀點。因此我們特意做了一下處理瑞眼,增加點抄襲成本龙宏,反正這不妨礙我們此次的演示目的。

如何實現(xiàn) cyclejs-like 的 reactive-view

image

首先實現(xiàn)一個 watchable 函數(shù)伤疙,可以將任意對象或數(shù)組银酗,變成可 watch 的,它有第二個參數(shù)掩浙,options花吟,其中 options.map 決定 set 階段時如何儲存到 target秸歧。

state 采用遞歸的方式厨姚,將整顆樹都 watch 起來。value 則只 watch value 字段键菱。

盡管 vue 被認(rèn)為不夠 fp谬墙,不過我們其實可以插上一些 fp 的翅膀,比如將 value 視為 monad经备,實現(xiàn) fmap, ap, bind 等函數(shù)拭抬。

image

fmap 是基于一個 watchable a,和 a -> b 的 f 函數(shù)侵蒙,構(gòu)造一個 watchable b 對象造虎。這里簡單 watch a,然后在賦值給b的階段,調(diào)用 f(a) 構(gòu)造新的值即可纷闺。

ap 則是 watchable f 和 watchable a算凿,構(gòu)造一個 watchable b,b 是 f(a) 的產(chǎn)物犁功。

bind 是 watchable a 加上類型未 a -> watchable b 的 f氓轰,實現(xiàn)基于上一個 value 的值構(gòu)造下一個 watchable 的功能。

至此浸卦,我們有了 state, value, 兩個構(gòu)造函數(shù)署鸡,有了 watch 監(jiān)聽函數(shù), 有了 fmap, ap, bind 基于 value 構(gòu)造下一個 value 的基本操作。

實現(xiàn) reactive view 用不到 computed,因此我們沒有去實現(xiàn)它靴庆。

vue 跟 rxjs 這種特殊的值时捌,可以直接衍生出 view。首先實現(xiàn)一個 combinaLatest([value])撒穷,得到一個在value范疇內(nèi)構(gòu)造數(shù)組的方式匣椰,然后通過 [[key, value]] ,從處理數(shù)組的方式中端礼,配合 fromEntries 衍生出 value

層面構(gòu)造object的方式禽笑。而virtual - ui - model 就是用特定的object表征對象。因此蛤奥,我們基于object

可以實現(xiàn) view佳镜,
它代表了一個在時間序列中動態(tài)輸出的視圖流,并且因為combinaLatest自動復(fù)用未變化的值凡桥,使的view->view輸出的結(jié)構(gòu)蟀伸,總是結(jié)構(gòu)共享的,利于 diff 算法缅刽。

image

實現(xiàn) combineArray:如果一個數(shù)組里存在一個 reactive value啊掏,那么它也返回一個 reactive array,它每次輸出一個純數(shù)組衰猛。如果數(shù)組里不包含 reactive value迟蜜,它什么也不包裝,直接返回該數(shù)組啡省。相當(dāng)于 Promise.all(list)娜睛,只不過它有可能不返回 promise/reactive-value。

image

有了 combineArray卦睹,可以實現(xiàn) combineObject畦戒,正如前面說的,就是 entries和 fromEntries 的轉(zhuǎn)換结序。

image

再封裝一下障斋,得到一個 combine 函數(shù),可以將任意結(jié)構(gòu)徐鹤,構(gòu)造成 reactive-value垃环,只要子結(jié)構(gòu)了包含 reactive-value,它就 wrap 成一個整體凳干。

現(xiàn)在我們除了 vue-like 的 reactivity api晴裹,還有 combine 函數(shù)了,可以去 combine react-element 了救赐。為什么是 combine react-element 涧团?因為我們就是要證明 vue 3.0 的 reactivity api 可以作為 library只磷,脫離 vue 來用。因此就用在其競爭對手 react 身上(其實是因為我比較熟悉 react)泌绣。

我們會將 jsx 的編譯函數(shù)從 React.createElement钮追,切換成我們自己構(gòu)造的 createElement。

image

createElement 將可能包含 reactivity-value 的 type, props, children阿迈,給 combine 起來元媚。檢測到 component 用 monad 的 bind,此時我們將組件描述為 bind 的 f 參數(shù)苗沧。檢測到 element 我們用 functor 的 fmap刊棕,將 props 映射成 react-element。

image

最后待逞,實現(xiàn)一個 map 函數(shù)甥角,用來 map 一個 reactive-value,既支持?jǐn)?shù)組识樱,也支持非數(shù)組嗤无。

準(zhǔn)備工作做好了,把它們 import 進(jìn)來怜庸。

image

回顧一下我們的 combineArray 是如何更新的当犯,它不是直接賦值,而是先淺拷貝割疾,再復(fù)制嚎卫。

image

?這意味著,它總是返回 immutable-list杈曲,因為它跟 immer 一樣 copy-on-write驰凛。

我們免費得到了一個行走的 immer胸懈,不需要 produce 包裹担扑。combine 一下,然后隨便改趣钱,watch 函數(shù)都會拿到結(jié)構(gòu)共享的 immutable data涌献。

如果沒有實現(xiàn)這一點,combine react-element 時首有,子樹直接被修改燕垃,react 進(jìn)行diff 時檢測不出來子樹有變化,就不會去更新視圖了井联。

現(xiàn)在可以實現(xiàn)一個 Counter 組件試試卜壕。

image

?看這個代碼,是不是覺得非常有趣烙常?既像 vue 那樣可以用 js 賦值操作轴捎,又像 react-hooks 那樣的形式,還像 cycle.js 一樣在組件內(nèi)部可以操作 reactive value。

它怎么做到自動更新視圖的呢侦副?

因為 let count = value(0)侦锯,它是一個 reactive-value。它被 handleIncre 和 handleDecre 修改秦驯,它同時用在了 jsx 里尺碰。我們的 createElement 會檢測到這個 jsx 結(jié)構(gòu)里包含一個 reactive-value,因此它會被整個 combine 起來译隘,成為一個大的響應(yīng)式的值 view.value亲桥。

image

前面我們將 jsx 編譯從 React.createElement 切換到我們的 createElement 函數(shù),因此 <Counter /> 組件不是返回 react-element固耘,而是返回我們的 reactive-value两曼,它是一個響應(yīng)式的值,可以被 watch玻驻。我們 watch 這個 <Counter />悼凑,然后拿到它真正的 react-element,再用 ReactDOM 渲染到 root 節(jié)點璧瞬。

看起來像下面那樣户辫。

image

只支持一個 counter,看起來可能是一個特例嗤锉,我們可以再實現(xiàn)一個 todo list渔欢。

image

?TodoApp 組件里構(gòu)造一個 reactive-state,然后傳遞給 TodoInput 和 Todos瘟忱。

image

?TodoInput 里構(gòu)造一個 reactive text奥额,作為局部狀態(tài),綁定到 input 元素访诱。

點擊 add 按鈕時垫挨,構(gòu)造一個 todo,直接 push 到 todos 里即可触菜。

其它用到 todos 的地方九榔,會自動檢測到 todos 變化而進(jìn)行局部渲染。比如我們的 Todos涡相。

image

?它通過 map 函數(shù)哲泊,將 reactive todos 映射成 Todo 組件,每當(dāng) todos 變化時催蝗,這個 map 函數(shù)就會自動再次執(zhí)行切威,然后 top-level 的 app 就會拿到一個 immutable vdom,除了 todos 以外丙号,其它結(jié)構(gòu)復(fù)用原來的引用先朦。

image

?Todo 里面很簡單且预,就是展示一下,支持 toggle 和 remove 什么的烙无。

整體看上去像下面那樣锋谐。

image

可以看到,我們從未調(diào)用 setState/setValue 等觸發(fā)函數(shù)截酷,只用到了原生 js 的方法和賦值操作涮拗。以一種符合直覺的方式,構(gòu)建了我們這個 reactive todo-list迂苛。

react 在這里只是起來了一個 renderer 的作用三热,理論上,用任意 vdom library 都行三幻。

如何用 combine 函數(shù)實現(xiàn)行走的 immer

image

上面的 test 是一個 reactive state就漾,里面深層節(jié)點里包含了 reactive-value。

mobx 作者的 immer 念搬,是現(xiàn)用現(xiàn)拋抑堡,nextState = produce(state, update)。

我們 reactive-state 的版本則是朗徊,draftState 不必限制在 update 函數(shù)里首妖,可以在外面隨意傳遞和使用,watch 函數(shù)拿到的總是 immutable 的爷恳。

我們構(gòu)造了 3 個方法有缆,分別深度更新不同的字段,然后隨機(jī)使用這些更新方法温亲。它們不會引起其它字段的引用變化棚壁,共享沒有變化的結(jié)構(gòu)。

image

?

比如栈虚,randomMethod a 只引起了 a 字段的更新袖外,因此 c 和 g 字段跟 prev 對比是相等的。

如何用 reactivity api 實現(xiàn) react-hooks 的機(jī)制节芥?

vue 3.0 的 reactivity api在刺,更多的是承擔(dān) connect, computed, combine 等結(jié)構(gòu)關(guān)聯(lián)的動作逆害,它沒有作為 source 去 produce data头镊。data 是外部傳入 state/value,以及 reactive-state 在別的地方被 mutate 出新數(shù)據(jù)魄幕。

而 react-hooks 其實是一個 producer相艇,它不斷的 re-execute 自身,產(chǎn)生很多次 return data 的過程纯陨。

react-hooks 跟 reactivity api 的結(jié)合坛芽,就得到了一個 producer + combinator留储。比如,我們要構(gòu)造一個 count咙轩,它不只是在 count.value += 1 的時候被動產(chǎn)生新的 value获讳,而是可以通過某個機(jī)制,不斷自動產(chǎn)生活喊。

image

這個結(jié)構(gòu)看起來跟 rxjs 倒很像丐膝。有 next, cleanup/unsubscribe,默認(rèn)自帶 startWith 操作符钾菊。后續(xù)我們可以實現(xiàn) merge, combine, concat, filter, take 等其它 operator帅矗。這樣直接 vue, react, rxjs 的 pattern 一家親了~

不過,額外引入 react-hooks煞烫,跟 vue-reactivity 并行浑此,會顯得很奇怪,應(yīng)該用后者實現(xiàn)前者的機(jī)制滞详。就是 re-run 時凛俱,重用 state/value,并且 state/value 的變化料饥,會引起函數(shù)的 re-run最冰。

useEffect 應(yīng)該是 watch 自身,是一個語法糖稀火。watch(self, effect)暖哨。

如此,區(qū)分出了兩種 reactivity 形態(tài)凰狞,一種是在 producer 外部的 free-order-reactivity篇裁,一種是在 producer 內(nèi)部的 fixed-order-reactivity。

實現(xiàn)起來很簡單赡若。

image

?實現(xiàn) 3 個增強(qiáng)函數(shù)的函數(shù)达布,resumable 增強(qiáng)函數(shù) re-run 自身的能力,referencable 增強(qiáng)函數(shù)持久化內(nèi)部狀態(tài)的能力逾冬。reactive 增強(qiáng)函數(shù)使用 reactivity api 的能力黍聂。

首先存在一個 env 內(nèi)部環(huán)境,它會被 resumeable, referencable, reactivie 等 enhancer 進(jìn)行拓展身腻。

image

reactivie 就是將 prodcuer 的返回值产还,掛載到 value[圖片上傳失敗...(image-2b7e3d-1563238657901)]

,因此外部總是拿到一個 reactive value嘀趟。

image

?useRef 的實現(xiàn)脐区,直接使用 referencable 提供的 storage 方法即可。

image

?useEffect 在使用 storage 方法時她按,通過 reactive enhancer 拿到了 value$牛隅,watch 它炕柔,并返回 unwatch。

image

?useReactive 在內(nèi)部構(gòu)造 reactive value/state媒佣,watch 它匕累,然后使用 resumable enhancer 提供的 resume 方法,觸發(fā)重新執(zhí)行默伍。

image

?然后用 useReactive哩罪,分別實現(xiàn) useState,和 useValue巡验。再用它們實現(xiàn)一個 interval 函數(shù)际插,可以輸出一個自行變化的 count。

image

?把 interval 用在我們之前的 Counter 組件里显设。

image

?效果框弛,有一個 tick 自動隨時間而變化,不需要額外的地方去 count.value += 1捕捂。

image

?如何用 reactivity api 實現(xiàn) rxjs-like 的功能瑟枫?

先實現(xiàn)一個 rxjs 那樣的 pipe,用法是 pipe(source, operator1, opeator2, operator3) 這類指攒。

image

?map operator 的實現(xiàn)慷妙,可以直接用 functor 的 fmap,參數(shù)映射一下 pipe 函數(shù)的要求允悦。

image

?因為 map 函數(shù)已經(jīng)定義過了膝擂,因此這個 map operator 只好改名為 mapx。

filter operator 就是通過 predicate 函數(shù)隙弛,有選擇的將 source[圖片上傳失敗...(image-b6bdd4-1563238657901)]

架馋。

image

?take 和 scan 則分別是內(nèi)部計數(shù)和累加acc,代碼都很簡單全闷。

image

將這些 operators 用在我們的 tick 上叉寂。

image

?輸出 10 個奇數(shù)的數(shù)組。如下圖所示总珠。

image

總結(jié)

需要說明的是屏鳍,目前的模擬是一個粗糙的做法,有很多沒有處理局服,比如 unwatch 的時機(jī)钓瞭,它幾乎一定會內(nèi)存泄露。需要更精細(xì)的去實現(xiàn)和控制腌逢,才能得到一個可用的形態(tài)降淮,當(dāng)下只是演示一下思路 。

這些 demo 只是演示一些能力搏讶。沒有考慮實際項目里怎么用佳鳖,不管大小,都不要用這個方案媒惕。

等有人基于這個思路做出一個完成度更好的庫或者框架系吩,再考慮吧。

到目前為止妒蔚,我們差不多填完了用 vue reativity api 實現(xiàn) immer-like, rxjs-like, react-hooks-like, cyclejs-like(就是最初的那個 reactive view) 的坑穿挨,應(yīng)該足以展示 vue reactivity api 是一個更加 primitive 的機(jī)制了(畢竟基于 Proxy)。

vue 3.0 reactivity api 的能力還不局限于上面演示的肴盏,感興趣的同學(xué)科盛,可以自行探索一下。

可以點擊這里訪問DEMO

引用

http://www.reibang.com/p/56e563982161

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菜皂,一起剝皮案震驚了整個濱河市贞绵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恍飘,老刑警劉巖榨崩,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異章母,居然都是意外死亡母蛛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進(jìn)店門乳怎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彩郊,“玉大人,你說我怎么就攤上這事蚪缀〗垢ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵椿胯,是天一觀的道長筷登。 經(jīng)常有香客問我,道長哩盲,這世上最難降的妖魔是什么前方? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮廉油,結(jié)果婚禮上惠险,老公的妹妹穿的比我還像新娘。我一直安慰自己抒线,他們只是感情好班巩,可當(dāng)我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嘶炭,像睡著了一般抱慌。 火紅的嫁衣襯著肌膚如雪逊桦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天抑进,我揣著相機(jī)與錄音强经,去河邊找鬼。 笑死寺渗,一個胖子當(dāng)著我的面吹牛匿情,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播信殊,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼炬称,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涡拘?” 一聲冷哼從身側(cè)響起玲躯,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鲸伴,沒想到半個月后府蔗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡汞窗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年姓赤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仲吏。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡不铆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出裹唆,到底是詐尸還是另有隱情誓斥,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布许帐,位于F島的核電站劳坑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏成畦。R本人自食惡果不足惜距芬,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望循帐。 院中可真熱鬧框仔,春花似錦、人聲如沸拄养。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至跛梗,卻和暖如春寻馏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茄袖。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工操软, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留嘁锯,地道東北人宪祥。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像家乘,于是被迫代替她去往敵國和親蝗羊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,666評論 2 350

推薦閱讀更多精彩內(nèi)容