自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端框架:性能與靈活性的取舍

開發(fā) 前端
本文將介紹一款名為legendapp的狀態(tài)管理庫,他與其他狀態(tài)管理庫設(shè)計理念上有很大不同。

大家好,我卡頌。

針對「前端框架」,長期存在著各種紛爭。其中爭論比較大的是下面兩項:

  • 性能之爭
  • API設(shè)計之爭

比如,各大新興框架都會掏出benchmark?證明自己優(yōu)秀的運行時性能,在這些benchmark中React通常是墊底的存在。

在API?設(shè)計上,Vue愛好者認為:“更多的API約束了開發(fā)者,不會因為團隊成員水平的差異造成代碼質(zhì)量較大的差異”。

而React?愛好者則認為:“Vue?大量的API?限制了靈活性,JSX yyds”。

上述討論歸根結(jié)底是框架「性能」與「靈活性」的取舍。

本文將介紹一款名為[1]的狀態(tài)管理庫,他與其他狀態(tài)管理庫設(shè)計理念上有很大不同。

圖片

在React?中合理使用legendapp,可以極大提升應(yīng)用的運行時性能。

但本文的目的并不僅僅是「介紹一個狀態(tài)管理庫」,而是與你一起感受「隨著性能提高,框架靈活性發(fā)生的變化」。

React的性能優(yōu)化

React?性能確實不算太好,這是不爭的事實。原因在于React自頂向下的更新機制。

每次狀態(tài)更新,React都會從根組件開始深度優(yōu)先遍歷整棵組件樹。

既然遍歷方式是固定的,那么如何優(yōu)化性能呢?答案是「尋找遍歷時可以跳過的子樹」。

什么樣的子樹可以跳過遍歷呢?顯然是「沒有發(fā)生變化的子樹」。

在React中,「變化」主要由下面3個要素造成:

  • state
  • props
  • context

他們都可能改變UI?,或者觸發(fā)useEffect。

所以,一棵子樹中如果存在上述3個要素的改變,可能會發(fā)生變化,也就不能跳過遍歷。

從「變化」的角度,我們再來看看React中的性能優(yōu)化API,對于下面2個:

  • useMemo
  • useCallback

他們的本質(zhì)是 —— 減少props的變化。

對于下面2個:

  • PureComponent
  • React.memo

他們的本質(zhì)是 —— 直接告訴React這個組件沒有變化,你不用再去檢查上述3個要素了。

狀態(tài)管理庫能做的優(yōu)化

了解了React的性能優(yōu)化,我們再來看看狀態(tài)管理庫能為「性能優(yōu)化」做些什么呢。

性能瓶頸主要發(fā)生在更新時,所以性能優(yōu)化的方向主要有兩個:

  • 減少不必要的更新
  • 減少每次更新時要遍歷的子樹

像Redux?語境下的useSelector走的就是第一條路。

對于后一條路,「減少更新時遍歷的子樹」通常意味著「減少上文介紹的3要素的變化」。

PS:黃玄開發(fā)的React Forget?,是一個「可以產(chǎn)生等效于useMemo、useCallback代碼的編譯器」,目的就是減少三要素中props的變化。

狀態(tài)管理庫在這方面能發(fā)揮的地方很有限,因為不管狀態(tài)管理庫如何巧妙的封裝,也無法掩蓋「他操作的其實是一個React狀態(tài)」這一事實。

比如,雖然Mobx為React?帶來了「細粒度更新」,但并不能帶來與Vue?中「細粒度更新」相匹配的性能,因為Mobx最終觸發(fā)的是自頂向下的更新。

legendapp的思路

本文要介紹的legendapp也走的是第二條路,但他的理念蠻特別的 —— 如果減少3要素的數(shù)量,那不就能減少3要素的變化么?

舉個極端的例子,如果一個龐大的應(yīng)用中一個狀態(tài)都沒有,那更新時整棵組件樹都能被跳過。

下面是個Hook實現(xiàn)的計數(shù)器例子,useInterval每秒觸發(fā)一次回調(diào),回調(diào)中會觸發(fā)更新:

function Counter() {
const [count, setCount] = useState(1)

useInterval(() => {
setCount(v => v + 1)
}, 1000)

return <div>Count: {count}</div>
}

根據(jù)3要素法則,Counter中包含名為count的state,且每秒發(fā)生變化,則更新時Counter不會被跳過(表現(xiàn)為Counter每秒都會render)。

下面是使用legendapp改造的例子:

function Counter() {
const count = useObservable(1)

useInterval(() => {
count.set(v => v + 1)
}, 1000)

return <div>Count: {count}</div>
}

在這個例子中,使用legendapp?提供的useObservable?方法定義狀態(tài)count。

Counter?只會render?一次,后續(xù)即使count?變化,Counter?也不會render。

在線Demo[2]。

這是如何辦到的呢?

在legendapp?源碼中,useObservable方法代碼如下:

function useObservable(initialValue) {
return React.useMemo(() => {

}, []);
}

通過包裹依賴項為空的React.useMemo,useObservable返回的實際是個「永遠不會變的值」。

既然返回的不是state?,那Counter?組件中就不包含3要素(state?、props?、context?)中的任何一個,當然不會render了。

我們將這個思路推廣開,如果整個應(yīng)用中所有狀態(tài)都通過useObservable?定義,那不就意味著整個應(yīng)用都不存在state,那么更新時整棵組件樹不都能跳過了么?

也就是說,legendapp在React?原有更新機制基礎(chǔ)上,實現(xiàn)了一套基于「細粒度更新」的完整更新流程,最大限度擺脫React的影響。

legendapp的原理

接下來我們再聊聊legendapp狀態(tài)更新的實現(xiàn)。

在傳統(tǒng)的React例子中:

function Counter() {
const [count, setCount] = useState(1)

useInterval(() => {
setCount(v => v + 1)
}, 1000)

return <div>Count: {count}</div>
}

count變化,造成Counter組件render,render時count是新的值,所以返回的div中count是新的值。

而在legendapp例子中,Counter只會render一次,count如何更新呢?

function Counter() {
const count = useObservable(1)

useInterval(() => {
count.set(v => v + 1)
}, 1000)

return <div>Count: {count}</div>
}

實際上,useObservable返回的count并不是一個數(shù)字,而是一個叫做Text的組件:

const Text = React.memo(function ({ data }) {

});

在Text組件中,會監(jiān)聽count的變化。

當count變化后,會通過內(nèi)部定義的useReducer觸發(fā)一次React更新。

雖然React的更新是自頂向下遍歷整棵組件樹,但是整個應(yīng)用中只有Text組件中存在狀態(tài)且發(fā)生變化,所以除Text組件外其他子樹都會被跳過。

性能與易用性的取舍

現(xiàn)在我們知道在legendapp中文本節(jié)點如何更新。

但JSX非常靈活,除了文本節(jié)點,還有比如:

  • 條件語句

如:

isShow ? <A/> : <B/>
  • 自定義屬性

如:

<div className={isFocus ? 'text-blue' : ''}></div>

這些形式的變化該如何監(jiān)聽,并觸發(fā)更新呢?

為此,legendapp提供了自定義組件Computed:

<Computed>
<span
className={showChild.get() ? 'text-blue' : ''}
>
{showChild.get() ? 'true' : 'false'}
</span>
</Computed>

對應(yīng)的React語句:

<span className={showChild ? 'text-blue' : ''}>
{showChild ? 'true' : 'false'}
</span>

Computed?相當于一個容器,會監(jiān)聽children?中的狀態(tài)變化,并觸發(fā)React更新。

文本節(jié)點對應(yīng)的Text組件可以類比為「被Computed包裹的文本內(nèi)容」:

<Computed>{文本內(nèi)容}</Computed>

除此之外,還有些更具語意化的標簽(本質(zhì)都是Computed的封裝),比如用于條件語句的Show:

<Show if={showChild}>
<div>Child element</div>
</Show>

對應(yīng)的React語句:

{showChild && (
<div>Child element</div>
)}

還有用于數(shù)組遍歷的<For/>組件等。

到這一步你應(yīng)該發(fā)現(xiàn)了,雖然我們利用legendapp?提高了運行時性能,但也引入了如Computed?、Show?等新的API。

你是愿意框架更靈活、有更多想象力,還是愿意犧牲靈活性,獲得更高的性能?

這就是本文想表達的「性能與易用性的取舍」。

總結(jié)

用過Solid.js?的同學會發(fā)現(xiàn),引入legendapp的React在API?上已經(jīng)無限接近Solid.js了。

事實上,當Solid.js?選擇結(jié)合React與「細粒度更新」,并在性能上作出優(yōu)化的那一刻起,就決定了他的最終形態(tài)就是如此。

legendapp? + React已經(jīng)在運行時做到了很高的性能,如果想進一步優(yōu)化,一個可行的方向是「編譯時優(yōu)化」。

如果朝著這個路子繼續(xù)前進,在不舍棄「虛擬DOM」的情況下,就會與Vue3無限接近。

如果更極端點,舍棄了「虛擬DOM」,那么就會與Svelte無限接近。

每個框架都在性能與靈活性上作出了取舍,以討好他們的目標受眾。

參考資料

[1]legendapp:https://www.legendapp.com/open-source/state/hooks/。

[2]在線Demo:https://codesandbox.io/s/legend-state-primitives-140tmg。

責任編輯:姜華 來源: 魔術(shù)師卡頌
相關(guān)推薦

2009-06-08 09:57:45

編程語言編程語言性能高級編程語言

2010-02-01 18:23:54

Python

2013-02-26 10:44:26

2009-01-01 22:08:15

企業(yè)網(wǎng)絡(luò)提高

2010-02-23 17:04:32

Python編程語言

2016-11-08 13:50:57

2012-03-07 15:22:02

2009-01-22 19:03:32

服務(wù)器虛擬化VMware

2024-05-15 08:54:04

C++類型限定符代碼

2015-12-31 09:44:56

公有云谷歌云評測

2010-07-20 09:18:48

云計算靈活性

2009-08-25 16:11:12

Repeater控件

2009-12-09 10:31:14

SOA質(zhì)量SOA靈活性

2012-02-24 09:03:11

云計算虛擬化

2010-07-22 10:08:39

JavaFXJava

2014-04-30 15:52:59

紅帽

2012-09-25 13:45:36

Marvell

2010-01-06 12:16:46

Ubuntu軟件包

2014-05-08 13:31:43

移動安全協(xié)作系統(tǒng)

2010-01-12 15:33:26

CC++
點贊
收藏

51CTO技術(shù)棧公眾號