新聞中心
不知大家有沒(méi)有聽(tīng)過(guò)Preact?這個(gè)框架,就算沒(méi)聽(tīng)過(guò)Preact?那也應(yīng)該聽(tīng)過(guò)React吧?

創(chuàng)新互聯(lián)專(zhuān)業(yè)為企業(yè)提供雞西網(wǎng)站建設(shè)、雞西做網(wǎng)站、雞西網(wǎng)站設(shè)計(jì)、雞西網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、雞西企業(yè)網(wǎng)站模板建站服務(wù),十年雞西做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
一字之差,preact比react?多了個(gè)p?。?tīng)起來(lái)咋不像啥好話呢)
這個(gè)P代表的是 Performance,高性能版React的意思。Preact一開(kāi)始是CodePen上的一個(gè)小項(xiàng)目,有點(diǎn)類(lèi)似于咱們國(guó)內(nèi)常見(jiàn)的《三百行代碼帶你實(shí)現(xiàn)個(gè)React》這類(lèi)文章,用最少的代碼實(shí)現(xiàn)了React的最基本功能,然后放到CodePen上供大家學(xué)習(xí)。
當(dāng)然這是很多年前的事了,那時(shí)候這種東西很容易火,想想N年前你看過(guò)的第一篇《三百行實(shí)現(xiàn)個(gè)Vue》、《三百行實(shí)現(xiàn)個(gè)React》之類(lèi)的文章是不是競(jìng)爭(zhēng)對(duì)手很少、很容易引發(fā)大量的關(guān)注度。
CORPORATE CULTURE
不過(guò)現(xiàn)在不行了,太卷!這類(lèi)文章隔三差五的就能看到一篇,同質(zhì)化嚴(yán)重,導(dǎo)致大家都有點(diǎn)審美疲勞了。
但在那個(gè)年代Preact就是這么火起來(lái)的,三百行實(shí)現(xiàn)了個(gè)React引發(fā)大量關(guān)注度之后,作者覺(jué)得自己做的這玩意好像還挺不錯(cuò)的哈!于是開(kāi)始繼續(xù)完善,完善后拿去一測(cè)試:性能簡(jiǎn)直完爆React呀!我這玩意不僅體積比你小、性能還比你高。就這樣作者開(kāi)始有些膨脹了、開(kāi)始飄了!
那我就給這個(gè)框架起個(gè)名叫Preact吧!
Performance?版的React!
Preact 簡(jiǎn)介
打開(kāi)Preact官網(wǎng),映入眼簾的便是它的最大賣(mài)點(diǎn):
只有3KB?大小、并且與React?擁有相同的API?。真的只有3KB?么?虛擬DOM、Diff?算法、類(lèi)組件、Hooks?… 這些就算實(shí)現(xiàn)的再怎么巧妙也需要很多代碼才行吧?我們直接用Vite?來(lái)創(chuàng)建一個(gè)Preact項(xiàng)目來(lái)試下:
npm create vite
如果屏幕前的你用的是VSCode這個(gè)編輯器的話,可以安裝一下Import Cost這個(gè)插件:
安裝好之后我們來(lái)看一下主文件(main.jsx):
臥槽?gizpped?真的只有3.幾K!不過(guò)這算法有點(diǎn)雞賊啊,來(lái)了個(gè)向下取整:
這讓我想起了最近非?;鸬腡urbopack比Vite快十倍的宣傳口號(hào),遭尤大怒懟:1k 組件的案例下有數(shù)字的四舍五入問(wèn)題,Turbopack 的 15ms 被向下取整為 0.01s,而到了 Vite 這里 87ms 被向上取整為 0.09s。這把本來(lái)接近 6 倍的差距擴(kuò)大到了 10 倍。
不過(guò)即使這樣,3.8K?依然是一個(gè)很驚人的成就。是不是只是render?這個(gè)函數(shù)就占了3.8K???我們?cè)僖c(diǎn)東西試試:
難以置信!引了這么多hooks?居然只多加了0.1K?!我還是不太相信用0.1K?的代碼就能實(shí)現(xiàn)出React Hooks來(lái),肯定是用了什么特殊的算法專(zhuān)門(mén)針對(duì)了這一場(chǎng)景做了優(yōu)化,我們按照官網(wǎng)的寫(xiě)法來(lái)重新引一下:
這回體積明顯增大了不少:
不過(guò)咋感覺(jué)自己跟個(gè)杠精似的呢 人家說(shuō)了3KB我卻非要以各種方式證明肯定不止3KB,這樣不好。Preact真的已經(jīng)很輕量了,一般人想要實(shí)現(xiàn)這么多功能還真做不到只用這么少的代碼,Preact的P肯定還是名不虛傳的
不過(guò)剛剛試了下Vue,Vue好像就沒(méi)有針對(duì)這種場(chǎng)景做專(zhuān)門(mén)的優(yōu)化,不僅沒(méi)優(yōu)化反而還劣化了:
實(shí)際上只引入某幾個(gè)函數(shù)的話Vue?沒(méi)有這么大,這是把Vue?全量引入的大小,尤大還不快跟人學(xué)學(xué)。
Preact Signals
說(shuō)到"學(xué)",Preact原本一直都是React的忠實(shí)粉絲,可最近它卻開(kāi)發(fā)了一個(gè)叫做@preact/signals的東西,這是干嘛的?Preact的創(chuàng)始人Jason Miller以及Preact DevTools的創(chuàng)始人Marvin Hagemeister共同寫(xiě)了篇博客:《Introducing Signals》
點(diǎn)開(kāi)文章,首先映入眼簾的便是這樣一個(gè)案例:
import { signal, computed } from "@preact/signals";
const count = signal(0);
const double = computed(() => count.value * 2);
function Counter() {
return (
);
}等等!這個(gè).value?、這個(gè)computed?、以及這個(gè)在jsx?的大括號(hào){}?中不用寫(xiě).value的語(yǔ)法…
怎么這么似曾相識(shí)呢?好像在哪里見(jiàn)過(guò)類(lèi)似的寫(xiě)法:
與hooks?不同,signals?可以在組件內(nèi)部或外部使用。signals?在類(lèi)組件也可以很好的運(yùn)行,因此您可以按照自己的節(jié)奏引入它們,并根據(jù)現(xiàn)狀,在幾個(gè)組件中試用它們,并隨著時(shí)間的推移逐漸采用它們?!?nbsp;Preact團(tuán)隊(duì)
那這樣不是越寫(xiě)越Vue?了嗎?還叫什么Preact?啊,叫Vreact多好!
尤雨溪:這還真特娘的是個(gè)好主意!我這就把拉你進(jìn) Vue 核心群里來(lái)!
我們來(lái)看看Preact?團(tuán)隊(duì)為何要實(shí)現(xiàn)個(gè)P?版的Composition API:
- 易沖突的全局狀態(tài)
- 混亂的上下文
- 尋求更好的狀態(tài)管理
- 卓越的性能
聽(tīng)說(shuō)最近尤大?被罵了,為啥被罵呢?因?yàn)楹孟裼写巫止?jié)邀請(qǐng)了尤大?直播,那尤大?肯定得借此機(jī)會(huì)好好宣傳一下Vue?啊!不過(guò)你光說(shuō)Vue?有多好,觀眾可能無(wú)法感受到。就像如果七龍珠直接讓超級(jí)賽亞人出場(chǎng),并且用那個(gè)戰(zhàn)斗力探測(cè)儀顯示一個(gè)戰(zhàn)力:
雖說(shuō)憑這個(gè)確實(shí)能讓人感受到超級(jí)賽亞人很強(qiáng),但如果要是能有個(gè)對(duì)比的話那才是最完美的劇情,所以才有了大反派弗利薩的出場(chǎng)機(jī)會(huì):
同理,尤大如果光在那羅列數(shù)據(jù)那肯定不如有個(gè)對(duì)比來(lái)的直觀,那就把React拉來(lái)對(duì)比一番唄!既然是為了宣傳Vue,那必須得拿Vue的優(yōu)點(diǎn)跟React的缺點(diǎn)比啦!這樣的對(duì)比難免會(huì)有失偏頗,讓React的粉絲們怒不可遏,在群里瘋狂批判尤大。
在一捧一踩(黑React)的宣傳過(guò)程中呢,尤大花費(fèi)最多時(shí)間宣傳的就是以下兩點(diǎn):
- 避免了React Hooks的一些心智負(fù)擔(dān)
- 性能比React強(qiáng)
其實(shí)這兩點(diǎn)多少還是有點(diǎn)有失偏頗,因?yàn)閂ue?在解決了一種心智負(fù)擔(dān)的同時(shí)又帶來(lái)了另一種心智負(fù)擔(dān),而且性能也要看場(chǎng)景的,尤大只強(qiáng)調(diào)了對(duì)Vue有利的場(chǎng)景來(lái)宣傳…
不過(guò)React?在某些層面來(lái)講確實(shí)有些劍走偏鋒了哈,導(dǎo)致性能不是特別理想。Preact也是這么認(rèn)為的,他們還特意搞了張火焰圖:
左邊用的是Preact Hooks?,右邊用的是Composition API?… 哦不,是Preact Signals???梢钥吹絊ignals?的表現(xiàn)完勝Hooks!
那Preact的老師React有在React里實(shí)現(xiàn)Vue的計(jì)劃嗎?答案是否定的,自從Preact Signals發(fā)布后大家就瘋狂@Dan,Dan看完后直接來(lái)了句:這與React的發(fā)展理念不是很吻合。(潛臺(tái)詞:我們才不會(huì)在React里實(shí)現(xiàn)Vue呢)
其實(shí)我覺(jué)得也是,React?的發(fā)展理念本來(lái)就跟Vue走的是完全不同的兩種路線,夸張點(diǎn)說(shuō)就是道不同不相為謀。
那肯定有人說(shuō):不對(duì)呀,Vue3的Composition API?不是抄襲的React么?
這么說(shuō)吧:大佬們借鑒的是思路,菜鳥(niǎo)們借鑒的才是代碼。了解過(guò)Vue、React他倆底層實(shí)現(xiàn)的朋友們應(yīng)該都清楚他倆的差距有多大。
尤雨溪在某次采訪時(shí)說(shuō)過(guò)Vue3一開(kāi)始本打算實(shí)現(xiàn)成類(lèi)組件,既然是類(lèi)那就離不開(kāi)裝飾器的話題,尤大說(shuō)他們甚至都已經(jīng)實(shí)現(xiàn)出來(lái)了一版類(lèi)組件寫(xiě)法的Vue3。只不過(guò)他覺(jué)得這樣相對(duì)于Vue2而言除了對(duì)TS的支持度之外幾乎沒(méi)有其他什么特別明顯的優(yōu)勢(shì)。
而且裝飾器提案發(fā)展了N年卻遲遲未能落地,尤大覺(jué)得這樣遙遙無(wú)期,而且就算真的在將來(lái)的某一天落地了,是不是也已經(jīng)與現(xiàn)在TS實(shí)現(xiàn)的那版裝飾器天差地別了?
Angular用裝飾器用的好好的那是因?yàn)槿思覐?qiáng)制要求使用TS,但Vue顯然不可能這樣做。而且為了防止未來(lái)裝飾器有變動(dòng)(其實(shí)最近已經(jīng)Stage3的裝飾器已經(jīng)和TS裝飾器不一樣了),許多曾經(jīng)使用裝飾器語(yǔ)法的庫(kù)為了規(guī)避這個(gè)風(fēng)險(xiǎn)也已經(jīng)改用了別的寫(xiě)法,如:MobX、React DnD等…
正當(dāng)尤雨溪為此抓耳撓腮、夜不能寐之時(shí),React Hooks橫空出世了!這種函數(shù)式組件瞬間就讓尤大眼前一亮,他腦袋里的燈泡在那一剎那間被點(diǎn)亮了:
這不就是自己一直苦苦尋找、對(duì)TS友好、方便代碼復(fù)用、語(yǔ)法簡(jiǎn)潔、低耦合的解決方案么!
但實(shí)際上吧,尤大只是參考了這種函數(shù)式的設(shè)計(jì),如今的Composition API原理與React Hooks相去甚遠(yuǎn)。真要借鑒的話,尤雨溪已經(jīng)大大方方承認(rèn)了是受到了React Hooks的啟發(fā),代碼層面借鑒的是Meteor Tracker、nx-js/observer-util、salesforce/observable-membrane這三個(gè)庫(kù)。響應(yīng)式庫(kù)其實(shí)早已不新鮮了,只是之前尤大沒(méi)能跳出Vue2的思維限制,直到看到了React Hooks才想到可以這樣寫(xiě),然后再一調(diào)研發(fā)現(xiàn)市面上早就有了函數(shù)式的響應(yīng)式庫(kù),Composition API就是這么來(lái)的。
不過(guò)他在Composition API?之前確實(shí)模仿了React的原理設(shè)計(jì)出來(lái)了vue-hooks,以用來(lái)探索這種函數(shù)式組件的可行性。
不過(guò)好在后來(lái)他發(fā)現(xiàn)了Meteor Tracker、nx-js/observer-util、salesforce/observable-membrane這幾個(gè)庫(kù)并及時(shí)懸崖勒馬,沒(méi)有在這個(gè)方向上繼續(xù)深挖,不然的話Vue3可能就要變成套殼React了。
那究竟為什么沒(méi)有在此方向繼續(xù)深挖呢?難道說(shuō)那仨庫(kù)的解決方案比React Hooks還要好嗎?對(duì)此我只想說(shuō):
拋開(kāi)了場(chǎng)景談好壞都是在耍流氓
這兩種方案各有優(yōu)缺點(diǎn),巧合的是:雙方彼此間的優(yōu)點(diǎn)恰恰好好就是對(duì)方身上的缺點(diǎn)。典型的性格互補(bǔ)么這不是:
有人喜歡內(nèi)向的、有人喜歡外向的、但也有人想當(dāng)一個(gè)縫合怪:為啥不能內(nèi)外雙向呢?該內(nèi)向的時(shí)候就內(nèi)向,該外向的時(shí)候就外向唄!Preact就是這樣想的,他們單獨(dú)提供了一個(gè)叫@preact/signals的包,你要是更在意性能呢,那就用@preact/signals、你要是更在意類(lèi)似React的開(kāi)發(fā)體驗(yàn)?zāi)?,那就不用唄!
用法
Preact?版的composition api主要分為三個(gè)部分:
- @preact/signals-core
- @preact/signals
- @preact/signals-react
從命名上來(lái)看,@preact/signals-core?應(yīng)該是與框架無(wú)關(guān)的核心實(shí)現(xiàn)、@preact/signals?是給Preact?的特供產(chǎn)品、而@preact/signals-react?則是給React提供的特供產(chǎn)品。
我們先來(lái)看一下核心實(shí)現(xiàn)的用法,這是他們README文件里給出的第一個(gè)例子:
import { signal } from "@preact/signals-core";
const counter = signal(0);
// Read value from signal, logs: 0
console.log(counter.value);
// Write to a signal
counter.value = 1;非常好理解,就是把原來(lái)composition api?里的ref?換成了signal,這里就不過(guò)多贅述了,來(lái)看下一個(gè)案例:
const counter = signal(0);
const effectCount = signal(0);
effect(() => {
console.log(counter.value);
// Whenever this effect is triggered, increase `effectCount`.
// But we don't want this signal to react to `effectCount`
effectCount.value = effectCount.peek() + 1;
});
這個(gè)effect也和composition api里的effect如出一轍,不過(guò)有同學(xué)可能會(huì)問(wèn)了:composition api里沒(méi)有effect呀?你說(shuō)的是watchEffect嗎?我這里表述的可能不是特別準(zhǔn)確,準(zhǔn)確來(lái)講的話應(yīng)該是和@vue/reactivity里的effect如出一轍。
那么問(wèn)題來(lái)了:@vue/reactivity不就是composition api嗎?其實(shí)他倆確實(shí)非常的…容易混淆,準(zhǔn)確來(lái)講@vue/reactivity是可以運(yùn)行在完全脫離vue的環(huán)境之下的,而composition api是根據(jù)vue的環(huán)境進(jìn)行的進(jìn)一步更好用的封裝。composition api包含了@vue/reactivity。
那composition api和@vue/composition-api又有啥區(qū)別呢?區(qū)別就是composition api只是一個(gè)概念,而@vue/composition-api是一個(gè)實(shí)現(xiàn)了composition api的項(xiàng)目。當(dāng)初尤雨溪提出composition api的時(shí)候(那時(shí)候還不叫composition api,好像叫什么functional base api)遭到了大量質(zhì)疑的聲音,于是有個(gè)大佬就用Vue2現(xiàn)有的API實(shí)現(xiàn)了一版尤雨溪的提案,尤雨溪覺(jué)得這玩意非常不錯(cuò)!你們老噴我是因?yàn)槟銈儧](méi)有體驗(yàn)過(guò)函數(shù)式的好,你們先用用試試,試完了保證你們直呼真香!于是聯(lián)系該作者把Vue2版的composition api合并到Vue的倉(cāng)庫(kù)中并發(fā)布為@vue/composition-api。
但誰(shuí)也不會(huì)用愛(ài)發(fā)電對(duì)不,剛開(kāi)始當(dāng)個(gè)娛樂(lè)項(xiàng)目給你宣傳了,時(shí)間一長(zhǎng)也沒(méi)啥收益,該作者也就不維護(hù)了。此時(shí)另一位大佬出現(xiàn)了,他說(shuō)既然沒(méi)人維護(hù)了那就交給我吧!他就是肝帝AntFu:
一整年就兩三天是滅著的,剩下的時(shí)間無(wú)論刮風(fēng)還是下雨,都無(wú)法阻擋大佬提交代碼的腳步。
甚至那兩三天我都懷疑是有什么不可抗力導(dǎo)致的,比方說(shuō)來(lái)臺(tái)風(fēng)斷電啦或者在飛機(jī)上沒(méi)法提交,下了飛機(jī)直接就進(jìn)入另一個(gè)時(shí)區(qū)(第二天)啦之類(lèi)的原因,他甚至比尤雨溪都勤快:
不過(guò)拿他倆比有點(diǎn)不太公平哈,尤大有家有孩子,而且還要帶領(lǐng)兩個(gè)團(tuán)隊(duì)(Vue、Vite),寫(xiě)代碼的時(shí)間自然會(huì)少很多。
而傅佬年輕沒(méi)結(jié)婚沒(méi)孩子、也無(wú)需帶領(lǐng)團(tuán)隊(duì)啥的,自然就會(huì)有很多時(shí)間做自己喜歡做的事情。不過(guò)我翻了一下尤大迭代最瘋狂的2016年,也依然沒(méi)我傅哥勤快:
這就是我傅哥為何能如此高產(chǎn)的原因。
有點(diǎn)扯遠(yuǎn)了哈,沒(méi)接觸過(guò)@vue/reactivity的effect同學(xué)暫且先把它理解為composition api的watchEffect,在這里開(kāi)始出現(xiàn)了一個(gè)與composition api不太一樣的api了哈,.peek()是什么鬼?為了幫助大家快速理解這玩意,我們需要對(duì)比一下composition api里兩個(gè)相似功能的api:watch和watchEffect。
這倆api?功能相似但各有優(yōu)缺點(diǎn),我們只說(shuō)watchEffect?不如watch的其中一個(gè)缺點(diǎn):無(wú)法精確控制到底監(jiān)聽(tīng)了哪個(gè)響應(yīng)式變量。
比方說(shuō)我們寫(xiě)了這樣一段邏輯:
import { ref, watchEffect } from 'vue';
const a = ref(0);
const b = ref(0);
watchEffect(() => {
console.log(a.value);
b.value++;
});每當(dāng)我們改動(dòng)a.value?的值時(shí),b.value?就會(huì)++?。這是我們希望的邏輯,但不幸的是,每當(dāng)我們改動(dòng)b.value?的值時(shí),b.value?還是會(huì)++?。這在watch里還是很好實(shí)現(xiàn)的:
import { ref, watch } from 'vue';
const a = ref(0);
const b = ref(0);
watch(a, value => {
console.log(value);
b.value++;
});但在watchEffect?那段代碼里就相當(dāng)于在watch中寫(xiě)了這樣一段代碼:
import { ref, watch } from 'vue';
const a = ref(0);
const b = ref(0);
watch([a, b], ([valueA, valueB]) => {
console.log(valueA);
b.value = valueB + 1;
});Vue?的方案是既提供一個(gè)自動(dòng)收集依賴(lài)的watchEffect?,同時(shí)也提供一個(gè)手動(dòng)收集依賴(lài)的watch。
而Preact?的方案則是只提供一個(gè)effect?(類(lèi)似Vue的watchEffect),如果你寫(xiě)出類(lèi)似上面那樣的代碼:
import { signal, effect } from '@preact/signals-core';
const a = signal(0);
const b = signal(0);
effect(() => {
console.log(a.value);
b.value++;
});那就直接報(bào)錯(cuò)給你看:
為什么會(huì)報(bào)錯(cuò)呢?了解過(guò)響應(yīng)式原理的同學(xué)應(yīng)該不難理解,就是觸發(fā)getter?的時(shí)候又會(huì)觸發(fā)setter?,而觸發(fā)了setter?又會(huì)導(dǎo)致重新運(yùn)行effect函數(shù)導(dǎo)致死循環(huán)。
那為啥Vue?那邊的代碼沒(méi)死循環(huán)呢?這是因?yàn)閂ue?做了這樣一層判斷:如果你在effect? / watchEffect?里觸發(fā)了setter?,那便不會(huì)觸發(fā)對(duì)應(yīng)的effect? / watchEffect函數(shù),這樣就可以避免死循環(huán)了。
那Preact?沒(méi)做這樣的處理怎么辦呢?那我們就避免在effect?里既對(duì)signal進(jìn)行取值操作同時(shí)又對(duì)它進(jìn)行賦值操作唄!
不過(guò)這樣做肯定是不行的哈,你這太不專(zhuān)業(yè)了,所有成熟的響應(yīng)式庫(kù)沒(méi)有哪個(gè)會(huì)放著這個(gè)問(wèn)題不去解決的。比方說(shuō)Solid.js,他就有一個(gè)叫untrack的函數(shù),假如我們?cè)趀ffect里想要獲取到一個(gè)響應(yīng)式的值但卻并不想它被收集到依賴(lài)?yán)锩嫒ゾ涂梢詫?xiě)成這樣:
import { createSignal, createEffect, untrack } from "solid-js";
const [a, setA] = createSignal(0);
const [b, setB] = createSignal(0);
createEffect(() => {
console.log(a());
console.log(untrack(b));
});這樣只有a?改變時(shí)會(huì)觸發(fā)effect?函數(shù),b則不會(huì)。如果你能理解上面這段代碼的話,那相信你肯定能理解下面這段代碼:
import { signal, effect } from '@preact/signals-core';
const a = signal(0);
const b = signal(0);
effect(() => {
console.log(a.value);
b.value = b.peek() + 1;
});我還專(zhuān)門(mén)去查了一下peek?是啥意思,是偷窺?的意思。有時(shí)候覺(jué)得老外起的api?名翻譯過(guò)來(lái)還蠻有意思的,就是說(shuō)我在effect?里需要獲取到某個(gè)響應(yīng)式變量的值,但直接獲取會(huì)被追蹤到,所以我不直接獲取,我要“偷窺”一眼它的值,這樣就不會(huì)被追蹤到啦?。ㄟ@個(gè)api雖然很調(diào)皮,但有些略顯猥瑣)
接下來(lái)看下一個(gè)api:computed?。就我不說(shuō)你們都能猜到這是干啥的,這就是Vue?的那個(gè)computed,直接看例子就不解釋了:
import { signal, computed } from "@preact/signals-core";
const name = signal("Jane");
const surname = signal("Doe");
const fullName = computed(() => name.value + " " + surname.value);
// Logs: "Jane Doe"
console.log(fullName.value);
// Updates flow through computed, but only if someone
// subscribes to it. More on that later.
name.value = "John";
// Logs: "John Doe"
console.log(fullName.value);下一個(gè)api是effect?,其實(shí)在.peek()?那個(gè)“偷窺”案例中就已經(jīng)用過(guò)effect?了,它就是@vue/reactivity?里的effect,也不過(guò)多解釋了,直接上案例:
import { signal, computed, effect } from "@preact/signals-core";
const name = signal("Jane");
const surname = signal("Doe");
const fullName = computed(() => name.value + " " + surname.value);
// Logs: "Jane Doe"
effect(() => console.log(fullName.value));
// Updating one of its dependencies will automatically trigger
// the effect above, and will print "John Doe" to the console.
name.value = "John";import { signal, computed, effect } from "@preact/signals-core";
const name = signal("Jane");
const surname = signal("Doe");
const fullName = computed(() => name.value + " " + surname.value);
// Logs: "Jane Doe"
const dispose = effect(() => console.log(fullName.value));
// Destroy effect and subscriptions
dispose();
// Update does nothing, because no one is subscribed anymore.
// Even the computed `fullName` signal won't change, because it knows
// that no one listens to it.
surname.value = "Doe 2";接下來(lái)這個(gè)api?可能會(huì)有些令大家陌生了,叫batch,分批處理的意思,來(lái)看如下案例:
import { signal, computed, effect, batch } from "@preact/signals-core";
const name = signal("Jane");
const surname = signal("Doe");
const fullName = computed(() => name.value + " " + surname.value);
// Logs: "Jane Doe"
effect(() => console.log(fullName.value));
// Combines both signal writes into one update. Once the callback
// returns the `effect` will trigger and we'll log "Foo Bar"
batch(() => {
name.value = "Foo";
surname.value = "Bar";
});有一定開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)應(yīng)該一下子就能看出這段代碼想表達(dá)什么意思了(如果看不懂的話去反思一下),就是當(dāng)我們修改值的時(shí)候是同步出發(fā)對(duì)應(yīng)的effect函數(shù)的,所以我們?nèi)绻B著改兩次就會(huì)連續(xù)運(yùn)行兩次,我寫(xiě)了一個(gè)簡(jiǎn)化版的案例給大家看一下:
import { signal, effect } from "@preact/signals-core";
const a = signal(0);
effect(() => console.log(a.value));
a.value++;
a.value++;控制臺(tái)打印結(jié)果:
就挺讓人無(wú)語(yǔ)的…… 這也能水個(gè)API?出來(lái)?人家Vue?默認(rèn)就是分批處理的,我們?cè)赩ue?里寫(xiě)一段同樣的代碼來(lái)看看Vue是怎么運(yùn)行的:
import { ref, watchEffect } from 'vue';
const a = ref(0);
watchEffect(() => console.log(a.value));
a.value++;
a.value++;控制臺(tái)打印結(jié)果:
不過(guò)之前咱們不是說(shuō)Vue的響應(yīng)式依賴(lài)是@vue/reactivity么?Composition API是Vue在@vue/reactivity的基礎(chǔ)上再次封裝,讓它變得更好用更適合Vue項(xiàng)目。那會(huì)不會(huì)是它封裝了批處理才導(dǎo)致這樣的結(jié)果的呢?我們先不用import xxx from 'vue'這種形式了,這樣的話用的是Composition API,我們這次用@vue/reactivity再來(lái)試一把:
import { ref, effect } from '@vue/reactivity';
const a = ref(0);
effect(() => console.log(a.value));
a.value++;
a.value++;果不其然,這次的結(jié)果終于和Preact保持一致了:
誤會(huì)了哈!我還尋思@preact/signals-core?也太不專(zhuān)業(yè)了,人家@vue/reactivity默認(rèn)就支持的東西……
不過(guò)既然@vue/reactivity?默認(rèn)也是同步的,那怎么分批處理呢?想讓它像@preact/signals-core這樣:
import { signal, effect, batch } from "@preact/signals-core";
const a = signal(0);
effect(() => console.log(a.value));
batch(() => {
a.value++;
a.value++;
})在@vue/reactivity中要想要達(dá)到同樣效果的話… 關(guān)鍵是這個(gè)@vue/reactivity連個(gè)文檔都沒(méi)有!Vue官網(wǎng)上的Composition API是又封裝了一層,用法已經(jīng)不一樣了。比方說(shuō)Composition API里的watch在@vue/reactivity里就沒(méi)有,而且watchEffect和effect表現(xiàn)也不太一致,@vue/reactivity的README寫(xiě)的也特別簡(jiǎn)陋:
機(jī)翻一下:
就很無(wú)奈,我想知道這個(gè)庫(kù)怎么用就只能去看看它的TS?定義,看看都有哪些API?以及都有哪些用法。哪怕不像Vue?那樣有個(gè)專(zhuān)門(mén)的官網(wǎng),那你在README?里寫(xiě)幾個(gè)簡(jiǎn)單的事例也行啊!就像@preact/signals-core那樣,能耽誤你幾小時(shí)?
吐槽歸吐槽,想知道咋用還是得去看代碼,在了src/effect.ts后我發(fā)現(xiàn)這樣一段代碼:
果然還是和Composition API?里的watchEffect?參數(shù)不一致,我們能看到有個(gè)lazy?字段,從名字上來(lái)看應(yīng)該就是它了吧。我還特意去Vue?官網(wǎng)看了一下watchEffect?的第二個(gè)參數(shù)都有哪些字段,watchEffect?就沒(méi)有l(wèi)azy?這個(gè)字段,取而代之的是flush字段:
用法這么大差異,連個(gè)文檔都不寫(xiě)。尤大,你是想讓每個(gè)用@vue/reactivity的人都去從源碼里找答案么?算了不吐槽了,咱們繼續(xù)來(lái)看例子:
import { ref, effect } from "@vue/reactivity";
const a = ref(0);
effect(() => console.log(a.value), { lazy: true });
a.value++;
a.value++;加了{(lán) lazy: true }以后控制臺(tái)啥都不打印了!尤大你是要?dú)馑牢已?!那這個(gè)lazy到底是用來(lái)干啥的?可能是用來(lái)代替Composition API里的watch的吧?wacth會(huì)自動(dòng)執(zhí)行一次,effect則不會(huì)這樣。
那也不對(duì)啊,watch只是剛開(kāi)始的時(shí)候不會(huì)自動(dòng)執(zhí)行一次,但當(dāng)依賴(lài)變化時(shí)還是會(huì)運(yùn)行啊,這怎么連運(yùn)行都不運(yùn)行了?不是你別讓我猜呀!想知道你這庫(kù)咋用就兩種方式:要么看源碼要么就靠猜…… 那如果不是lazy的話那就是scheduler字段?想看看你這咋用,結(jié)果你給我來(lái)個(gè)這:
文檔不寫(xiě)就算了,你還定義了一堆a(bǔ)ny類(lèi)型… 這特么到底咋用???好像以前看過(guò)的《Vue.js設(shè)計(jì)與實(shí)現(xiàn)》里有寫(xiě)過(guò),不過(guò)那本書(shū)搬家放在哪里想不起來(lái)了,等我找到后再把例子給補(bǔ)上。
之前還想吐槽@preact/signals-core不專(zhuān)業(yè),Vue早就支持的功能它還要專(zhuān)門(mén)出一個(gè)API。現(xiàn)在看來(lái)還是我太年輕,與框架無(wú)關(guān)的@vue/reactivity連個(gè)文檔都沒(méi)有,都不知道怎么支持這個(gè)批量更新,不專(zhuān)業(yè)的反而是@vue/reactivity。
咱們繼續(xù)來(lái)看下一個(gè)案例:
import { signal, computed, effect, batch } from "@preact/signals-core";
const counter = signal(0);
const double = computed(() => counter.value * 2);
const tripple = computed(() => counter.value * 3);
effect(() => console.log(double.value, tripple.value));
batch(() => {
counter.value = 1;
// Logs: 2, despite being inside batch, but `tripple`
// will only update once the callback is complete
console.log(double.value);
});
// Now we reached the end of the batch and call the effect這是啥意思呢?就是我們?cè)赽atch函數(shù)里訪問(wèn)了一個(gè)計(jì)算屬性,按理說(shuō)要等batch函數(shù)運(yùn)行完了才會(huì)去更新,但這個(gè)計(jì)算屬性依賴(lài)的值在batch里剛剛被改過(guò),為了讓我們能拿到正確的值,不等batch執(zhí)行完就直接更新這個(gè)計(jì)算屬性。但也不是所有依賴(lài)counter的計(jì)算屬性都會(huì)被更新,沒(méi)在batch函數(shù)里被訪問(wèn)到的tripple就會(huì)等batch函數(shù)運(yùn)行完畢后再去進(jìn)行更新。
batch函數(shù)還可以嵌套著寫(xiě):
import { signal, computed, effect, batch } from "@preact/signals-core";
const counter = signal(0);
effect(() => console.log(counter.value));
batch(() => {
batch(() => {
// Signal is invalidated, but update is not flushed because
// we're still inside another batch
counter.value = 1;
});
// Still not updated...
});
// Now the callback completed and we'll trigger the effect.當(dāng)最外層的batch函數(shù)運(yùn)行完成時(shí)才會(huì)更新對(duì)應(yīng)的值。
React 及 Preact
core?的核心部分講完了,那就繼續(xù)看看@preact/signals?以及@preact/signals-react吧!它倆用法都一樣:
import { signal } from "@preact/signals-react";
const count = signal(0);
function CounterValue() {
// Whenver the `count` signal is updated, we'll
// re-render this component automatically for you
return Value: {count.value}
;
}import { useSignal, useComputed } from "@preact/signals-react";
function Counter() {
const count = useSignal(0);
const double = useComputed(() => count.value * 2);
return (
);
}就有點(diǎn)類(lèi)似于在React?里寫(xiě)Vue的那種感覺(jué)。
后續(xù)
晚上回家一頓翻,終于找著了《Vue.js設(shè)計(jì)與實(shí)現(xiàn)》這本書(shū),聲明一下本文真不是這本書(shū)的軟廣告,多賣(mài)出去一本我也不會(huì)得到什么分成。真就是我寫(xiě)那個(gè)例子的時(shí)候找不到文檔又不知道咋用,README讓去看TS聲明結(jié)果看了個(gè)any…
我是真沒(méi)耐心去特別仔細(xì)的研究@vue/reactivity的源碼,我覺(jué)得理解了大概的原理就行不必那么死摳細(xì)節(jié),畢竟咱們一不靠賣(mài)源碼課賺錢(qián)、二也不負(fù)責(zé)維護(hù)Vue、三也不像一些大佬似的沒(méi)事就以鉆研為樂(lè)、四也不至于研究完源碼就能升職加薪什么的…
不過(guò)好在我之前看過(guò)那本書(shū)里面寫(xiě)的挺詳細(xì)的好像有scheduler、lazy之類(lèi)的字段是用來(lái)干嘛的并且還給出了實(shí)現(xiàn)以及用例。我又看了一遍響應(yīng)式那章,之前靠猜以為lazy是用來(lái)模仿watch的,結(jié)果寫(xiě)了{(lán) lazy: true }之后直接不運(yùn)行了,這是因?yàn)閷?xiě)了{(lán) lazy: true }就從自動(dòng)擋變手動(dòng)擋了!返回一個(gè)函數(shù)讓你自己去決定啥時(shí)候運(yùn)行:
import { ref, effect } from '@vue/reactivity'
const a = ref(0)
const fn = effect(() => console.log(a.value), { lazy: true })
a.value++
fn()打印結(jié)果:
那這樣寫(xiě)有什么意義呢?這樣寫(xiě)確實(shí)沒(méi)什么意義,本來(lái)能自動(dòng)運(yùn)行的函數(shù)非要讓你手動(dòng)運(yùn)行。這樣做的意義主要是為了實(shí)現(xiàn)computed的,咱們想要的是@preact/signals-core里的batch批處理功能,書(shū)中的scheduler選項(xiàng)接收一個(gè)參數(shù),但實(shí)測(cè)當(dāng)前最新版本的@vue/reactvity沒(méi)有任何參數(shù):
import { ref, effect } from '@vue/reactivity'
const a = ref(0)
effect(
() => console.log(a.value),
{
scheduler (fn) {
console.log(fn)
}
}
)
a.value++打印結(jié)果:
盲猜可能是版本變化導(dǎo)致的用法不一致行為,我們把@vue/reactivity?的版本改成3.0后再來(lái)打印一下:
這回有值了,那為什么會(huì)把這個(gè)參數(shù)刪掉呢?我們只能從CHANGELOG里找答案了:
從有限的信息我們可以得知大概是從3.2?及后續(xù)版本刪掉了的,3.0.x及3.1.x與書(shū)中用法保持一致。
在書(shū)中scheduler的參數(shù)十分重要,書(shū)中就是基于這個(gè)參數(shù)來(lái)實(shí)現(xiàn)的批處理能力。想知道新用法么?我不告訴你!就是不寫(xiě)文檔嘿嘿!看源碼去吧!
這讓我突然想起尤大在某紀(jì)錄片中吐槽有些人就是不看文檔,我也想吐個(gè)槽:你特么倒是寫(xiě)呀!
沒(méi)辦法了,先鉆研一下源碼吧!經(jīng)過(guò)我一段時(shí)間的鉆研呢,大概得出來(lái)了一個(gè)結(jié)論:在3.2?之后effect?的返回值其實(shí)就相當(dāng)于3.2?之前scheduler的參數(shù):
// 3.2 以前
effect(
() => {},
{
scheduler (fn) {
console.log(fn)
}
}
)
// 3.2 之后(含 3.2)
const fn = effect(
() => {},
{
scheduler () {
console.log(fn)
}
}
)
那我們就可以根據(jù)這一變化來(lái)重寫(xiě)書(shū)中給出的調(diào)度執(zhí)行的案例了:
import { ref, effect } from '@vue/reactivity'
const jobQueue = new Set()
const p = Promise.resolve()
let isFlushing = false
function flushJob() {
if (isFlushing) return
isFlushing = true
p.then(() => {
jobQueue.forEach(job => job())
}).finally(() => {
isFlushing = false
})
}
const a = ref(0)
const fn = effect(
() => console.log(a.value),
{
scheduler () {
jobQueue.add(fn)<
本文名稱(chēng):信仰崩了?Preact開(kāi)始采用Vue3的響應(yīng)式設(shè)計(jì)
文章來(lái)源:http://m.fisionsoft.com.cn/article/cdpjghd.html


咨詢(xún)
建站咨詢(xún)
