欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文詳解preact的高性能狀態(tài)管理Signals

 更新時(shí)間:2022年09月13日 10:49:33   作者:前端科代表張繼科  
這篇文章主要介紹了一文詳解preact的高性能狀態(tài)管理Signals,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的朋友可以參考一下

前言

Signals是一種用來描述狀態(tài)的方式,可以確保應(yīng)用程序保持快速,無(wú)論應(yīng)用程序變得多么的復(fù)雜。Signals基于響應(yīng)式原則,提供了優(yōu)秀的開發(fā)者工效學(xué),并且針對(duì)虛擬DOM優(yōu)化做了獨(dú)特實(shí)現(xiàn)。

Signals的核心在于,一個(gè)signal就是一個(gè)具有具有.value屬性的對(duì)象,并持有一些值。當(dāng)signal的value值發(fā)生變化時(shí),從一個(gè)組件中訪問signal的value屬性會(huì)自動(dòng)更新該組件。

除了直截了當(dāng)和易于編寫之外,無(wú)論你的應(yīng)用程序中有多少個(gè)組件,都還能確保狀態(tài)更新保持快速。Signals默認(rèn)是很快的,在幕后自動(dòng)為你優(yōu)化更新性能。

run in repl

import { render } from "preact";

import { signal, computed } from "@preact/signals";
const count = signal(0);
const double = computed(() => count.value * 2);

function Counter() {
  return (
    <button onClick={() => count.value++}>
      {count} x 2 = {double}
    </button>
  );
}
render(<Counter />, document.getElementById("app"));

Signals可以在組件內(nèi)部或者組件外部使用,它跟hooks不一樣。Signals也可以和hooks、類組件一起使用,所以你可以帶著你現(xiàn)有的知識(shí),按照你自己的節(jié)奏一步一步地引入它們。在一些組件中嘗試它們,并隨著時(shí)間的推移逐漸采用Signals。

哦順便說一下,我們一直堅(jiān)持我們的初心,為大家提供盡可能小的類庫(kù)。在Preact中使用signals最終只給你的軟件包體積增加了1.6KB。

如果你想跳過直接進(jìn)入學(xué)習(xí),請(qǐng)到我們的文檔中去深入了解signals。

signals解決了哪些問題?

在過去的幾年里,我們?cè)诟鞣N應(yīng)用程序和團(tuán)隊(duì)中工作,從小型創(chuàng)業(yè)公司到有數(shù)百名開發(fā)人員同時(shí)投入的巨石應(yīng)用。在這段時(shí)間里,核心團(tuán)隊(duì)的每個(gè)人都注意到了應(yīng)用程序狀態(tài)管理方式中反復(fù)出現(xiàn)的問題。

為了解決這些問題,我們提出了一些神奇的解決方案,但是即使是最好的解決方案,也需要手動(dòng)集成到框架中。因此,我們看到了開發(fā)者對(duì)采用這些解決方案時(shí)的猶豫不決,而更傾向于使用框架原本提供的狀態(tài)來構(gòu)建。

我們將Signals打造成一個(gè)引人注目的解決方案,將最佳性能、開發(fā)人員的工效學(xué)與框架的無(wú)縫集成結(jié)合起來。

全局狀態(tài)的斗爭(zhēng)

應(yīng)用程序的狀態(tài)一開始時(shí)通常是小而簡(jiǎn)單的,也許是幾個(gè)簡(jiǎn)單的useState hooks。隨著應(yīng)用程序的迭代,越來越多的組件需要訪問相同的狀態(tài),這些狀態(tài)最終被提升到一個(gè)共同的祖先組件上面。這種模式會(huì)重復(fù)多次,直到大部分的狀態(tài)最終都在組件樹的根組件附近。

這種場(chǎng)景給傳統(tǒng)的基于虛擬DOM的框架帶來了挑戰(zhàn),它們必須更新受狀態(tài)失效影響的整個(gè)樹。從本質(zhì)上來講,渲染性能是該樹中組件數(shù)量的函數(shù)。我們可以通過使用memo或者useMemo對(duì)組件樹的部分進(jìn)行記憶備忘來解決這個(gè)問題,這樣框架就會(huì)收到相同的對(duì)象。當(dāng)沒有變化時(shí),可以讓框架跳過渲染樹的某些部分。

雖然這在理論上聽起來很合理,但是實(shí)際情況往往要混亂得多。在實(shí)踐過程中,隨著代碼庫(kù)的增長(zhǎng),很難確定這些優(yōu)化應(yīng)該放在哪里。通常,即使是用心良苦的記憶優(yōu)化也會(huì)因?yàn)椴环€(wěn)定的依賴值而變得無(wú)效。由于hooks沒有可以分析的明確的依賴樹,所以工具不能幫助開發(fā)者診斷為什么依賴是不穩(wěn)定的。

上下文混亂

另一個(gè)常見的解決方法就是將狀態(tài)放到上下文中。這樣就可以通過跳過上下文提供者和消費(fèi)者之間的組件渲染來實(shí)現(xiàn)短路渲染。但是有一個(gè)問題:只有傳遞給上下文提供者的值可以被更新,而且只能作為一個(gè)整體。更新通過上下文暴露的對(duì)象上的屬性并不能更新該上下文的消費(fèi)者--細(xì)化更新是不可能的。解決這個(gè)問題的可行選項(xiàng)是將狀態(tài)分割成多個(gè)上下文,或者在上下文對(duì)象的任何屬性發(fā)生變化時(shí)通過克隆它來使其失效。

將狀態(tài)值轉(zhuǎn)移到上下文中,起初似乎是一個(gè)值得考慮的權(quán)衡,但是為了共享值而增加組件樹的大小,最終其弊端成為一個(gè)問題。業(yè)務(wù)邏輯最終不可避免地依賴于多個(gè)上下文值,這可能會(huì)迫使它在組件樹中的特定位置實(shí)現(xiàn)。在樹的中間添加一個(gè)訂閱上下文的組件是很昂貴的,因?yàn)樗鼫p少了更新上下文時(shí)可以跳過的組件數(shù)量。更重要的是,訂閱者下面的任何組件現(xiàn)在必須重新渲染。解決這個(gè)問題的唯一方法是大量使用記憶化,這又讓我們回到了記憶化固有的問題上。

尋找更好的方式來管理狀態(tài)

我們又回到了尋找下一代狀態(tài)基元的繪圖板上。我們想創(chuàng)造一些能解決當(dāng)前解決方案中問題的東西。手動(dòng)進(jìn)行框架集成、過渡依賴記憶化、對(duì)上下文的次優(yōu)使用以及缺乏可編程的可觀察性,這些都讓人感覺很落后。

開發(fā)者需要考慮選擇加入這些策略的性能。如果我們能扭轉(zhuǎn)這種情況,提供一個(gè)默認(rèn)快速的系統(tǒng),使最佳性能成為你必須努力選擇的東西,那會(huì)怎么樣?

我們對(duì)這些問題的答案是Signals。這是一個(gè)默認(rèn)快速的系統(tǒng),不需要在你的應(yīng)用程序中使用記憶化或其它技巧。Signals提供了細(xì)粒度狀態(tài)更新的好處,無(wú)論該狀態(tài)是全局的、通過props或者上下文傳遞的,還是在某個(gè)組件的局部。

通往未來的Signals

Signals背后的主要思想是,我們不是通過組件樹傳遞一個(gè)值,而是傳遞一個(gè)包含該值的signal對(duì)象(類似于ref)。當(dāng)一個(gè)signal的value屬性改變時(shí),signal本身保持不變。因此,signal可以被更新而不需要重新渲染它們所經(jīng)過的組件,因?yàn)榻M件看到的是signal而不是它的值。這讓我們跳過了渲染組件的昂貴工作,并立即跳到樹中實(shí)際訪問signal的value屬性的特定組件。

我們正在運(yùn)用這樣一個(gè)事實(shí):一個(gè)應(yīng)用程序的狀態(tài)圖通常比它的組件樹要淺得多。這樣可以做出更快的渲染,因?yàn)榕c組件樹相比,更新狀態(tài)圖所需的工作量要少得多。這種差異在瀏覽器中測(cè)量時(shí)最為明顯--下面的截圖顯示了同一個(gè)應(yīng)用程序兩次測(cè)量的DevTools Profiler軌跡:第一次使用hooks,第二次使用Signals。

signals版本大大優(yōu)于任何傳統(tǒng)的基于虛擬DOM的框架的更新機(jī)制。在我們測(cè)試的一些應(yīng)用程序中,signals的速度非???,以至于在火焰圖中根本難以找到它們。

signals的性能是翻轉(zhuǎn)的:signals不是通過記憶化或者選擇器來選擇性能,signals默認(rèn)就是快速的。有了signals,性能就可以選擇性不考慮(之前不使用signals)。

為了達(dá)到這樣的性能水平,signals是建立在這些關(guān)鍵原則之上的:

  • 默認(rèn)情況下是懶惰的:只觀察和更新目前在某處被使用到的signal--斷開連接的signal不會(huì)影響性能。
  • 最佳更新:如果一個(gè)signal的value沒有被修改,使用該signal的value的組件和effects就不會(huì)被更新,即使該signal的依賴關(guān)系已經(jīng)發(fā)生改變。
  • 最佳的依賴性跟蹤:框架為你跟蹤所有東西所依賴的signal--沒有像鉤子那樣的依賴性數(shù)組。
  • 直接訪問:在組件中訪問一個(gè)signal的value會(huì)自動(dòng)訂閱更新,不再需要選擇器或者h(yuǎn)ook。

這些原則使得signal很適合廣泛的使用場(chǎng)景,甚至與渲染用戶界面無(wú)關(guān)的場(chǎng)景。

將signals帶入Preact

在確認(rèn)了正確的狀態(tài)基元后,我們開始將其與Preact進(jìn)行連接。我們一直都很喜歡hooks,因?yàn)樗鼈兛梢灾苯釉诮M件中使用。與第三方狀態(tài)管理解決方案相比,這是一個(gè)具備人性化的優(yōu)勢(shì),后者通常依靠“選擇器”函數(shù)或?qū)⒔M件包裹在一個(gè)特殊的函數(shù)中來訂閱更新。

// Selector based subscription :(
function Counter() {
  const value = useSelector(state => state.count);
  // ...
}

// Wrapper function based subscription :(
const counterState = new Counter();

const Counter = observe(props => {
  const value = counterState.count;
  // ...
});

這兩種方法都不能讓我們感到滿意。選擇器的方法需要將所有的狀態(tài)訪問包裹在選擇器中,這對(duì)于復(fù)雜的或者嵌套的狀態(tài)來說變得很繁瑣。在函數(shù)中包裝組件的方法需要手工來包裝組件,這就帶來了一系列的問題,比如缺少組件名稱和靜態(tài)屬性。

在過去的幾年里,我們有機(jī)會(huì)與許多開發(fā)者密切合作。一個(gè)共同的掙扎,特別是對(duì)于那些剛接觸(p)react的人來說,像選擇器和包裝器這樣的概念是額外的范式,必須在感覺到每個(gè)狀態(tài)管理解決方案有成效之前學(xué)會(huì)。

理想情況下,我們不需要知道選擇器或包裝器函數(shù),可以直接訪問組件中的狀態(tài):

// 假設(shè)這是一個(gè)全局狀態(tài),整個(gè)應(yīng)用程序可以訪問:
let count = 0;

function Counter() {
 return (
   <button onClick={() => count++}>
     value: {count}
   </button>
 );
}

上面的代碼很清晰,很容易理解發(fā)生了什么,但是不幸的是,它并沒有發(fā)揮作用。當(dāng)點(diǎn)擊按鈕時(shí),組件并沒有更新,因?yàn)闆]有辦法知道count已經(jīng)改變。

我們無(wú)法將這個(gè)場(chǎng)景從我們的腦海中抹去。我們能做些什么來使如此清晰的模型成為現(xiàn)實(shí)呢?我們開始使用preact的可插拔渲染器,對(duì)各種想法和實(shí)現(xiàn)進(jìn)行原型設(shè)計(jì)。功夫不負(fù)有心人,我們最終找到了一個(gè)實(shí)現(xiàn)的方法:

// 假設(shè)這是一個(gè)全局狀態(tài),整個(gè)應(yīng)用程序可以訪問:
const count = signal(0);

function Counter() {
 return (
   <button onClick={() => count.value++}>
     Value: {count.value}
   </button>
 );
}

沒有選擇器,沒有封裝函數(shù),什么都沒有。訪問signal的value就足以讓組件知道它需要在該signal的value發(fā)生變化時(shí)進(jìn)行更新。在幾個(gè)應(yīng)用程序中測(cè)試了這個(gè)原型后,很明顯我們發(fā)現(xiàn)了一些事情。這樣寫代碼感覺很直觀,而且不需要任何精神體操來保持事物的最佳狀態(tài)。

我們的代碼可以跑得更快嗎?

我們本可以在這里停下來,按原來這樣發(fā)布signals,但這是Preact團(tuán)隊(duì):我們需要看看我們能把Preact的集成推動(dòng)到什么程度。在上面的Counter例子中,count的value只是用來顯示文本,這確實(shí)不應(yīng)該去重新渲染整個(gè)組件。如果我們不在signal的value變化時(shí)自動(dòng)重新渲染組件,而只重新渲染文本呢?更妙的是,如果我們完全繞過虛擬DOM,直接在DOM中更新文本,會(huì)怎么樣?

const count = signal(0);

// Instead of this:
<p>Value: {count.value}</p>

// … we can pass the signal directly into JSX:
<p>Value: {count}</p>

// … or even passing them as DOM properties:
<input value={count} />

所以,是的,我們也這樣做了。你可以在通常使用字符串的任何地方將signal直接傳入JSX。signal的value將被呈現(xiàn)為文本,當(dāng)signal發(fā)生變化時(shí),它將自動(dòng)更新自己。這也適用于props。

下一步

如果你很好奇并想直接進(jìn)入查看,請(qǐng)到我們的文檔中查看signals。我們很樂意聽到你將如何使用它們。

請(qǐng)記住,不要急于切換到signals。hooks將繼續(xù)被支持,而且它們與signals一起使用也很好!我們建議逐漸嘗試signals。我們建議逐漸嘗試使用signals,從一些組件開始,以適應(yīng)這些概念。

到此這篇關(guān)于一文詳解preact的高性能狀態(tài)管理Signals的文章就介紹到這了,更多相關(guān)preact狀態(tài)管理Signals內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論