日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
阿里三面:有Reactfiber,為什么不需要Vuefiber呢?

但如果被問:

  1. 有react fiber,為什么不需要 vue fiber呢;
  2. 之前遞歸遍歷虛擬dom樹被打斷就得從頭開始,為什么有了react fiber就能斷點(diǎn)恢復(fù)呢;

本文將從兩個框架的響應(yīng)式設(shè)計為切入口講清這兩個問題,不涉及晦澀源碼,不管有沒有使用過react,閱讀都不會有太大阻力。

什么是響應(yīng)式

無論你常用的是 react,還是 vue,“響應(yīng)式更新”這個詞肯定都不陌生。

響應(yīng)式,直觀來說就是視圖會自動更新。如果一開始接觸前端就直接上手框架,會覺得這是理所當(dāng)然的,但在“響應(yīng)式框架”出世之前,實(shí)現(xiàn)這一功能是很麻煩的。

下面我將做一個時間顯示器,用原生 js、react、vue 分別實(shí)現(xiàn):

1.原生js:

想讓屏幕上內(nèi)容變化,必須需要先找到dom(document.getElementById),然后再修改dom(clockDom.innerText)。









有了響應(yīng)式框架,一切變得簡單了

2.react:

對內(nèi)容做修改,只需要調(diào)用setState去修改數(shù)據(jù),之后頁面便會重新渲染。







3.vue:

我們一樣不用關(guān)注dom,在修改數(shù)據(jù)時,直接this.state=xxx修改,頁面就會展示最新的數(shù)據(jù)。



{{greet}}

現(xiàn)在是:{{time}}






react、vue的響應(yīng)式原理

上文提到修改數(shù)據(jù)時,react需要調(diào)用setState方法,而vue直接修改變量就行。看起來只是兩個框架的用法不同罷了,但響應(yīng)式原理正在于此。

從底層實(shí)現(xiàn)來看修改數(shù)據(jù):在react中,組件的狀態(tài)是不能被修改的,setState沒有修改原來那塊內(nèi)存中的變量,而是去新開辟一塊內(nèi)存;

而vue則是直接修改保存狀態(tài)的那塊原始內(nèi)存。

所以經(jīng)常能看到react相關(guān)的文章里經(jīng)常會出現(xiàn)一個詞"immutable",翻譯過來就是不可變的。

數(shù)據(jù)修改了,接下來要解決視圖的更新:react中,調(diào)用setState方法后,會自頂向下重新渲染組件,自頂向下的含義是,該組件以及它的子組件全部需要渲染;而vue使用Object.defineProperty(vue@3遷移到了Proxy)對數(shù)據(jù)的設(shè)置(setter)和獲?。╣etter)做了劫持,也就是說,vue能準(zhǔn)確知道視圖模版中哪一塊用到了這個數(shù)據(jù),并且在這個數(shù)據(jù)修改時,告訴這個視圖,你需要重新渲染了。

所以當(dāng)一個數(shù)據(jù)改變,react的組件渲染是很消耗性能的——父組件的狀態(tài)更新了,所有的子組件得跟著一起渲染,它不能像vue一樣,精確到當(dāng)前組件的粒度。

為了佐證,我分別用react和vue寫了一個demo,功能很簡單:父組件嵌套子組件,點(diǎn)擊父組件的按鈕會修改父組件的狀態(tài),點(diǎn)擊子組件的按鈕會修改子組件的狀態(tài)。

為了更好的對比,直觀展示渲染階段,沒用使用更流行的react函數(shù)式組件,vue也用的是不常見的render方法:

class Father extends React.Component{
state = {
fatherState:'Father-original state'
}
changeState = () => {
console.log('-----change Father state-----')
this.setState({fatherState:'Father-new state'})
}
render(){
console.log('Father:render')
return (

{this.state.fatherState}







)
}
}
class Child extends React.Component{
state = {
childState:'Child-original state'
}
changeState = () => {
console.log('-----change Child state-----')
this.setState({childState:'Child-new state'})
}
render(){
console.log('child:render')
return (

{this.state.childState}




)
}
}
ReactDOM.render(,document.getElementById('root'))



上面是使用react時的效果,修改父組件的狀態(tài),父子組件都會重新渲染:點(diǎn)擊change Father state,不僅打印了Father:render,還打印了child:render。

const Father = Vue.createApp({
data() {
return {
fatherState:'Father-original state',
}
},
methods:{
changeState:function(){
console.log('-----change Father state-----')
this.fatherState = 'Father-new state'
}
},
render(){
console.log('Father:render')
return Vue.h('div',{},[
Vue.h('h2',this.fatherState),
Vue.h('button',{onClick:this.changeState},'change Father state'),
Vue.h('hr'),
Vue.h(Vue.resolveComponent('child'))
])
}
})
Father.component('child',{
data() {
return {
childState:'Child-original state'
}
},
methods:{
changeState:function(){
console.log('-----change Child state-----')
this.childState = 'Child-new state'
}
},
render(){
console.log('child:render')
return Vue.h('div',{},[
Vue.h('h3',this.childState),
Vue.h('button',{onClick:this.changeState},'change Child state'),

])
}
})
Father.mount('#root')



上面使用vue時的效果,無論是修改哪個狀態(tài),組件都只重新渲染最小顆粒:點(diǎn)擊change Father state,只打印Father:render,不會打印child:render。

不同響應(yīng)式原理的影響

首先需要強(qiáng)調(diào)的是,上文提到的“渲染”“render”“更新“都不是指瀏覽器真正渲染出視圖。而是框架在javascript層面上,調(diào)用自身實(shí)現(xiàn)的render方法,生成一個普通的對象,這個對象保存了真實(shí)dom的屬性,也就是常說的虛擬dom。本文會用組件渲染和頁面渲染對兩者做區(qū)分。

每次的視圖更新流程是這樣的:

  1. 組件渲染生成一棵新的虛擬dom樹;
  2. 新舊虛擬dom樹對比,找出變動的部分;(也就是常說的diff算法)
  3. 為真正改變的部分創(chuàng)建真實(shí)dom,把他們掛載到文檔,實(shí)現(xiàn)頁面重渲染;

由于react和vue的響應(yīng)式實(shí)現(xiàn)原理不同,數(shù)據(jù)更新時,第一步中react組件會渲染出一棵更大的虛擬dom樹。

fiber是什么

上面說了這么多,都是為了方便講清楚為什么需要react fiber:在數(shù)據(jù)更新時,react生成了一棵更大的虛擬dom樹,給第二步的diff帶來了很大壓力——我們想找到真正變化的部分,這需要花費(fèi)更長的時間。js占據(jù)主線程去做比較,渲染線程便無法做其他工作,用戶的交互得不到響應(yīng),所以便出現(xiàn)了react fiber。

react fiber沒法讓比較的時間縮短,但它使得diff的過程被分成一小段一小段的,因?yàn)樗辛恕氨4婀ぷ鬟M(jìn)度”的能力。js會比較一部分虛擬dom,然后讓渡主線程,給瀏覽器去做其他工作,然后繼續(xù)比較,依次往復(fù),等到最后比較完成,一次性更新到視圖上。

fiber是一種新的數(shù)據(jù)結(jié)構(gòu)

上文提到了,react fiber使得diff階段有了被保存工作進(jìn)度的能力,這部分會講清楚為什么。

我們要找到前后狀態(tài)變化的部分,必須把所有節(jié)點(diǎn)遍歷。

在老的架構(gòu)中,節(jié)點(diǎn)以樹的形式被組織起來:每個節(jié)點(diǎn)上有多個指針指向子節(jié)點(diǎn)。要找到兩棵樹的變化部分,最容易想到的辦法就是深度優(yōu)先遍歷,規(guī)則如下:

  1. 從根節(jié)點(diǎn)開始,依次遍歷該節(jié)點(diǎn)的所有子節(jié)點(diǎn);
  2. 當(dāng)一個節(jié)點(diǎn)的所有子節(jié)點(diǎn)遍歷完成,才認(rèn)為該節(jié)點(diǎn)遍歷完成;

如果你系統(tǒng)學(xué)習(xí)過數(shù)據(jù)結(jié)構(gòu),應(yīng)該很快就能反應(yīng)過來,這不過是深度優(yōu)先遍歷的后續(xù)遍歷。根據(jù)這個規(guī)則,在圖中標(biāo)出了節(jié)點(diǎn)完成遍歷的順序。

這種遍歷有一個特點(diǎn),必須一次性完成。假設(shè)遍歷發(fā)生了中斷,雖然可以保留當(dāng)下進(jìn)行中節(jié)點(diǎn)的索引,下次繼續(xù)時,我們的確可以繼續(xù)遍歷該節(jié)點(diǎn)下面的所有子節(jié)點(diǎn),但是沒有辦法找到其父節(jié)點(diǎn)——因?yàn)槊總€節(jié)點(diǎn)只有其子節(jié)點(diǎn)的指向。斷點(diǎn)沒有辦法恢復(fù),只能從頭再來一遍。

以該樹為例:

在遍歷到節(jié)點(diǎn)2時發(fā)生了中斷,我們保存對節(jié)點(diǎn)2的索引,下次恢復(fù)時可以把它下面的3、4節(jié)點(diǎn)遍歷到,但是卻無法找回5、6、7、8節(jié)點(diǎn)。

在新的架構(gòu)中,每個節(jié)點(diǎn)有三個指針:分別指向第一個子節(jié)點(diǎn)、下一個兄弟節(jié)點(diǎn)、父節(jié)點(diǎn)。這種數(shù)據(jù)結(jié)構(gòu)就是fiber,它的遍歷規(guī)則如下:

  1. 從根節(jié)點(diǎn)開始,依次遍歷該節(jié)點(diǎn)的子節(jié)點(diǎn)、兄弟節(jié)點(diǎn),如果兩者都遍歷了,則回到它的父節(jié)點(diǎn);
  2. 當(dāng)一個節(jié)點(diǎn)的所有子節(jié)點(diǎn)遍歷完成,才認(rèn)為該節(jié)點(diǎn)遍歷完成;

根據(jù)這個規(guī)則,同樣在圖中標(biāo)出了節(jié)點(diǎn)遍歷完成的順序。跟樹結(jié)構(gòu)對比會發(fā)現(xiàn),雖然數(shù)據(jù)結(jié)構(gòu)不同,但是節(jié)點(diǎn)的遍歷開始和完成順序一模一樣。不同的是,當(dāng)遍歷發(fā)生中斷時,只要保留下當(dāng)前節(jié)點(diǎn)的索引,斷點(diǎn)是可以恢復(fù)的——因?yàn)槊總€節(jié)點(diǎn)都保持著對其父節(jié)點(diǎn)的索引。

同樣在遍歷到節(jié)點(diǎn)2時中斷,fiber結(jié)構(gòu)使得剩下的所有節(jié)點(diǎn)依舊能全部被走到。

這就是react fiber的渲染可以被中斷的原因。樹和fiber雖然看起來很像,但本質(zhì)上來說,一個是樹,一個是鏈表。

fiber是纖程

這種數(shù)據(jù)結(jié)構(gòu)之所以被叫做fiber,因?yàn)閒iber的翻譯是纖程,它被認(rèn)為是協(xié)程的一種實(shí)現(xiàn)形式。協(xié)程是比線程更小的調(diào)度單位:它的開啟、暫??梢员怀绦騿T所控制。具體來說,react fiber是通過requestIdleCallback這個api去控制的組件渲染的“進(jìn)度條”。

requesetIdleCallback是一個屬于宏任務(wù)的回調(diào),就像setTimeout一樣。不同的是,setTimeout的執(zhí)行時機(jī)由我們傳入的回調(diào)時間去控制,requesetIdleCallback是受屏幕的刷新率去控制。本文不對這部分做深入探討,只需要知道它每隔16ms會被調(diào)用一次,它的回調(diào)函數(shù)可以獲取本次可以執(zhí)行的時間,每一個16ms除了requesetIdleCallback的回調(diào)之外,還有其他工作,所以能使用的時間是不確定的,但只要時間到了,就會停下節(jié)點(diǎn)的遍歷。

使用方法如下:

const workLoop = (deadLine) => {
let shouldYield = false;// 是否該讓出線程
while(!shouldYield){
console.log('working')
// 遍歷節(jié)點(diǎn)等工作
shouldYield = deadLine.timeRemaining()<1;
}
requestIdleCallback(workLoop)
}
requestIdleCallback(workLoop);


requestIdleCallback的回調(diào)函數(shù)可以通過傳入的參數(shù)deadLine.timeRemaining()檢查當(dāng)下還有多少時間供自己使用。上面的demo也是react fiber工作的偽代碼。

但由于兼容性不好,加上該回調(diào)函數(shù)被調(diào)用的頻率太低,react實(shí)際使用的是一個polyfill(自己實(shí)現(xiàn)的api),而不是requestIdleCallback。

現(xiàn)在,可以總結(jié)一下了:React Fiber是React 16提出的一種更新機(jī)制,使用鏈表取代了樹,將虛擬dom連接,使得組件更新的流程可以被中斷恢復(fù);它把組件渲染的工作分片,到時會主動讓出渲染主線程。

react fiber帶來的變化

首先放一張在社區(qū)廣為流傳的對比圖,分別是用react 15和16實(shí)現(xiàn)的。這是一個寬度變化的三角形,每個小圓形中間的數(shù)字會隨時間改變,除此之外,將鼠標(biāo)懸停,小圓點(diǎn)的顏色會發(fā)生變化。

(戳這里是react15-stack在線地址|這里是react16-fiber)

實(shí)操一下,可以發(fā)現(xiàn)兩個特點(diǎn):

  1. 使用新架構(gòu)后,動畫變得流暢,寬度的變化不會卡頓;
  2. 使用新架構(gòu)后,用戶響應(yīng)變快,鼠標(biāo)懸停時顏色變化更快;

看到到這里先稍微停一下,這兩點(diǎn)都是fiber帶給我們的嗎——用戶響應(yīng)變快是可以理解的,但使用react fiber能帶來渲染的加速嗎?

動畫變流暢的根本原因,一定是一秒內(nèi)可以獲得更多動畫幀。但是當(dāng)我們使用react fiber時,并沒有減少更新所需要的總時間。

為了方便理解,我把刷新時的狀態(tài)做了一張圖:

上面是使用舊的react時,獲得每一幀的時間點(diǎn),下面是使用fiber架構(gòu)時,獲得每一幀的時間點(diǎn),因?yàn)榻M件渲染被分片,完成一幀更新的時間點(diǎn)反而被推后了,我們把一些時間片去處理用戶響應(yīng)了。

這里要注意,不會出現(xiàn)“一次組件渲染沒有完成,頁面部分渲染更新”的情況,react會保證每次更新都是完整的。

但頁面的動畫確實(shí)變得流暢了,這是為什么呢?

我把該項目的 代碼倉庫 down下來,看了一下它的動畫實(shí)現(xiàn):組件動畫效果并不是直接修改width獲得的,而是使用的transform:scale屬性搭配3D變換。如果你聽說過硬件加速,大概知道為什么了:這樣設(shè)置頁面的重新渲染不依賴上圖中的渲染主線程,而是在GPU中直接完成。也就是說,這個渲染主線程線程只用保證有一些時間片去響應(yīng)用戶交互就可以了。

-
+
{this.state.seconds}



修改一下項目代碼中152行,把圖形的變化改為寬度width修改,會發(fā)現(xiàn)即使用react fiber,動畫也會變得相當(dāng)卡頓,所以這里的流暢主要是CSS動畫的功勞。(內(nèi)存不大的電腦謹(jǐn)慎嘗試,瀏覽器會卡死)

react不如vue?

我們現(xiàn)在已經(jīng)知道了react fiber是在彌補(bǔ)更新時“無腦”刷新,不夠精確帶來的缺陷。這是不是能說明react性能更差呢?

并不是。孰優(yōu)孰劣是一個很有爭議的話題,在此不做評價。因?yàn)関ue實(shí)現(xiàn)精準(zhǔn)更新也是有代價的,一方面是需要給每一個組件配置一個“監(jiān)視器”,管理著視圖的依賴收集和數(shù)據(jù)更新時的發(fā)布通知,這對性能同樣是有消耗的;另一方面vue能實(shí)現(xiàn)依賴收集得益于它的模版語法,實(shí)現(xiàn)靜態(tài)編譯,這是使用更靈活的JSX語法的react做不到的。

在react fiber出現(xiàn)之前,react也提供了PureComponent、shouldComponentUpdate、useMemo,useCallback等方法給我們,來聲明哪些是不需要連帶更新子組件。

結(jié)語

回到開頭的幾個問題,答案不難在文中找到:

  1. react因?yàn)橄忍斓牟蛔恪獰o法精確更新,所以需要react fiber把組件渲染工作切片;而vue基于數(shù)據(jù)劫持,更新粒度很小,沒有這個壓力;
  2. react fiber這種數(shù)據(jù)結(jié)構(gòu)使得節(jié)點(diǎn)可以回溯到其父節(jié)點(diǎn),只要保留下中斷的節(jié)點(diǎn)索引,就可以恢復(fù)之前的工作進(jìn)度。

本文題目:阿里三面:有Reactfiber,為什么不需要Vuefiber呢?
文章路徑:http://www.5511xx.com/article/djesheg.html