摘要:基礎的理論概念這篇文章是我的一次嘗試,希望能夠形式化的介紹關于本身的一些理念模型。我對于此實際的理念模型是在每次的更新過程中返回下一個階段的狀態。的目標是提升對在動畫,布局以及手勢方面的友好度。我已經邀請了團隊的成員來對本文檔的準確性進行。
前言
本文主要是對收集到的一些官方或者其他平臺的文章進行翻譯,中間可能穿插一些個人的理解,如有錯誤疏漏之處,還望批評指正。筆者并未研究過源碼,只是希望本文成為那些inspire你的東西的一部分,從而在今后一起去探討和研究React Fiber。
注:絕大多數情況下,以下的第一人稱不代表譯者,而是對應文章的作者,請注意區分。
React basic 基礎的理論概念??這篇文章是我的一次嘗試,希望能夠形式化的介紹關于react本身的一些理念模型。目的在于基于演繹推理的方式,描述那些給我們靈感讓我們進行這樣的設計的源泉。
??當然,這里的一些設想是具有爭議的,實際的設計也許也會有bug或者疏漏。但是,這也是一個好的開始讓我們去形式化地談論這些。同時,如果你有更好的想法,也歡迎pr。以下讓我們沿著這個思路,從簡單到復雜的去思考這一系列問題,不必擔心,這里沒有太多具體的框架細節。
??實際的關于React的實現是充滿務實主義的,漸進式的,算法優化的,新老代碼交替的,各種調試工具以及任何你能想到的讓他變成更加有用的東西。當然,這些東西也像版本迭代一樣,它們的存在是短暫的,如果它們足夠有用,我們就會不斷的更新他們。再次聲明,實際的實現是非常非常復雜的。
轉換??React最核心的前提是,UI僅僅是數據->數據的映射。相同的輸入意味著相同輸出。非常簡單的純函數。
function NameBox(name) { return { fontWeight: "bold", labelContent: name }; }
"Sebastian Markb?ge" -> { fontWeight: "bold", labelContent: "Sebastian Markb?ge" };抽象
??但是,并不是所有的UI都能這樣做,因為,有些UI是非常復雜的。所以,很重要的一點是,UI能夠被抽象成許許多多可復用的小塊,同時不暴露這些小塊的內部實現細節。就像在一個函數中調用另一個函數一樣。
function FancyUserBox(user) { return { borderStyle: "1px solid blue", childContent: [ "Name: ", NameBox(user.firstName + " " + user.lastName) ] }; }
{ firstName: "Sebastian", lastName: "Markb?ge" } -> { borderStyle: "1px solid blue", childContent: [ "Name: ", { fontWeight: "bold", labelContent: "Sebastian Markb?ge" } ] };組合
??為了實現可復用這一特性,僅僅只是簡單復用葉子節點,每次都為它們創建一個新的容器是遠遠不夠的。同時我們需要在容器(container)這一層面構建抽象,并且組合其它抽象。在我看來,組合就是將兩個甚至多個抽象變成一個新的抽象。
function FancyBox(children) { return { borderStyle: "1px solid blue", children: children }; } function UserBox(user) { return FancyBox([ "Name: ", NameBox(user.firstName + " " + user.lastName) ]); }狀態
??UI并不僅僅是簡單的服務或者說業務中的邏輯狀態。事實上,對于一個特定的投影而言,很多狀態是具體的,但是對于其他投影,可能不是這樣。例如,如果你正在文本框中輸入,這些輸入的字符可以被復制到另外的tab或者移動設備上(當然你不想復制也沒問題,主要是為了和下一句的例子進行區分)。但是,諸如滾動條的位置這樣的數據,你幾乎從來不會想把它在多個投影中復制(因為在這臺設備上比如滾動條位置是200,但是在其他設備上滾動到200的內容通常來說肯定是不同的)。
??我們更趨向于將我們的數據模型變為不可變的。我們在最頂端將所有能更新狀態的函數串起來,把它們當作一個原子(說成事務可能更容易明白)來對待。
function FancyNameBox(user, likes, onClick) { return FancyBox([ "Name: ", NameBox(user.firstName + " " + user.lastName), "Likes: ", LikeBox(likes), LikeButton(onClick) ]); } // Implementation Details var likes = 0; function addOneMoreLike() { likes++; rerender(); } // Init FancyNameBox( { firstName: "Sebastian", lastName: "Markb?ge" }, likes, addOneMoreLike );
注意:這個例子通過副作用去更新狀態。我對于此實際的理念模型是在每次的更新過程中返回下一個階段的狀態。當然,不這樣做看起來要更簡單一點,但是在以后我們最終還是會選擇改變這個例子采用的方式(因為副作用的缺點太多了)。
緩存??我們知道,對于純函數而言,一次又一次相同的調用是非常浪費時間和空間的。我們可以對這些函數建立緩存的版本,追蹤最近一次調用的輸入和輸出。下一次就可以直接返回結果,不用再次計算。
function memoize(fn) { var cachedArg; var cachedResult; return function(arg) { if (cachedArg === arg) { return cachedResult; } cachedArg = arg; cachedResult = fn(arg); return cachedResult; }; } var MemoizedNameBox = memoize(NameBox); function NameAndAgeBox(user, currentTime) { return FancyBox([ "Name: ", MemoizedNameBox(user.firstName + " " + user.lastName), "Age in milliseconds: ", currentTime - user.dateOfBirth ]); }列表/集合
??大多數UI都是通過很多個列表組成,通過列表中的每個元素產生不同的值(比如data.map(item =>
??為了管理每個列表元素的狀態,我們可以創建一個Map來管理每個特定的列表元素。
function UserList(users, likesPerUser, updateUserLikes) { return users.map(user => FancyNameBox( user, likesPerUser.get(user.id), () => updateUserLikes(user.id, likesPerUser.get(user.id) + 1) )); } var likesPerUser = new Map(); function updateUserLikes(id, likeCount) { likesPerUser.set(id, likeCount); rerender(); } UserList(data.users, likesPerUser, updateUserLikes);
注意:現在我們有多個不同的輸入傳遞給FancyNameBox。那會破壞我們上一節提到的緩存策略,因為我們一次只能記憶一個值。(因為上面的memoize函數的形參只有一個)
續延??不幸的是,在UI中有太多的list相互嵌套,我們不得不用大量的模板代碼去顯式的管理它們。
??我們可以通過延遲執行將一部分的模板代碼移到我們的主要邏輯之外。例如,通過利用currying(可以通過bind實現)(當然我們知道這樣bind并沒有完整的實現currying)。然后我們通過在核心函數之外的地方傳遞狀態,這樣,我們就能擺脫對模板的依賴。
??這并沒有減少模板代碼,但是至少將它們移動到了核心邏輯之外。
function FancyUserList(users) { return FancyBox( UserList.bind(null, users) ); } const box = FancyUserList(data.users); const resolvedChildren = box.children(likesPerUser, updateUserLikes); const resolvedBox = { ...box, children: resolvedChildren };
譯注:這里當然可以采用
function FancyUserList(users) { return FancyBox( UserList(users, likesPerUser, updateUserLikes) ); }
??但是這樣擴展起來就很麻煩,想增加,刪除我們都需要去改FancyUserList里的代碼。最重要的是,如果我們想將likesPerUser和updateUserLikes換成其他的集合和函數的話,我們必須再創建一個函數,如:
function FancyUserList2(users) { return FancyBox( UserList(users, likesPerUser2, updateUserLikes2) ); }
當然,你肯定會想到,直接給FancyUserList設置成接收多個參數不就行了。但是這樣依然存在一個問題,那就是每次你需要用到FancyUserList的時候,都需要帶上所有的參數。要解決也是可以的,比如const foo = FancyUserList.bind(null, data.users),后面需要用的話,直接foo(bar1, func1), foo(bar2, func2)就行了。也實現了設計模式中我們常談到的分離程序中變與不變的部分。但是這樣的實現將bind操作交給了調用者,這一點上可以改進,就像示例中提到的那樣。
狀態映射??我們很早就知道,一旦我們看見相同的部分,我們能夠使用組合去避免一次又一次重復的去實現相同的部分。我們可以將提取出來那部分邏輯移動并傳遞給更低等級或者說更低層級的函數,這些函數就是我們經常復用的那些函數。
function FancyBoxWithState( children, stateMap, updateState ) { return FancyBox( children.map(child => child.continuation( stateMap.get(child.key), updateState )) ); } function UserList(users) { return users.map(user => { continuation: FancyNameBox.bind(null, user), key: user.id }); } function FancyUserList(users) { return FancyBoxWithState.bind(null, UserList(users) ); } const continuation = FancyUserList(data.users); continuation(likesPerUser, updateUserLikes);緩存映射
??想在緩存列表中緩存多個元素是比較困難的,你必須弄清楚一些在平衡緩存與頻率之間做得很好的緩存算法,然而這些算法是非常復雜的。
??幸運的是,在同一區域的UI通常是比較穩定的,不會變化的。
??在這里我們依然可以采用像剛剛那種緩存state的技巧,通過組合的方式傳遞memoizationCache
function memoize(fn) { return function(arg, memoizationCache) { if (memoizationCache.arg === arg) { return memoizationCache.result; } const result = fn(arg); memoizationCache.arg = arg; memoizationCache.result = result; return result; }; } function FancyBoxWithState( children, stateMap, updateState, memoizationCache ) { return FancyBox( children.map(child => child.continuation( stateMap.get(child.key), updateState, memoizationCache.get(child.key) )) ); } const MemoizedFancyNameBox = memoize(FancyNameBox);代數哲學
??你會發現,這有點像PITA(一種類似肉夾饃的食物),通過幾個不同層次的抽象,將你需要的東西(值/參數)一點一點的加進去。有時這也提供了一種快捷的方式,能在不借助第三方的條件下在兩個抽象之間傳遞數據。在React里面,我們把這叫做context.
??有時候數據之間的依賴并不像抽象樹那樣整齊一致。例如,在布局算法中,在完整的確定所有字節點的位置之前,你需要知道各個子節點矩形區域的大小。
Now, this example is a bit "out there". I"ll use Algebraic Effects as proposed for ECMAScript. If you"re familiar with functional programming, they"re avoiding the intermediate ceremony imposed by monads.
譯注:FP理解不深,所以上面段就不翻譯了,以免誤導
function ThemeBorderColorRequest() { } function FancyBox(children) { const color = raise new ThemeBorderColorRequest(); return { borderWidth: "1px", borderColor: color, children: children }; } function BlueTheme(children) { return try { children(); } catch effect ThemeBorderColorRequest -> [, continuation] { continuation("blue"); } } function App(data) { return BlueTheme( FancyUserList.bind(null, data.users) ); }React Fiber體系結構
譯注:為了比較形象的闡釋,故這里將React Stack vs Fiber的視頻貼在這,而不是放在閱讀更多里面。由于在youtube上,為了方便查看,這里錄制了一張gif(有點大,18M,下載時請耐心等待)。
簡介??React Fiber是一個正在進行中的對React核心算法的重寫。它是過去兩年React團隊研究成果的一個頂峰。
??React Fiber的目標是提升對在動畫,布局以及手勢方面的友好度。它最重要的特性叫做"增量式/漸進式"渲染:即,將渲染工作分割為多個小塊進行,并在各個幀之間傳播。
??其它關鍵的特性包括,1.擁有了暫停,中止以及當有更新來臨的時候重新恢復工作的能力。2.不同的能力對于不同類型的更新分配不同的優先級。3.新的并發原語。
關于本文檔??在Fiber中引入了幾個新的概念,這些概念僅僅只看代碼是很難真的體會的。本文檔最初只是我在React項目組時的收集,收集一些我整理Fiber的實現的時候的筆記。隨著筆記的增多,我意識到這可能對其他人來說也是一個有益的資源。(譯注:本文檔的作者acdlite是Facebook開發組的一名成員,并不屬于React框架的開發組(這里指實際工作中,而不是gh上的team)。React團隊的leader,舊的核心算法及新的核心算法的提出者是sebmarkbage)
??我將嘗試盡可能用簡單的語言來描述,避免一些不必要的術語。在必要時也會給出一些資源的鏈接。
??請注意我并不是React團隊的一員,也不具備足夠的權威。所以這并不是一份官方文檔。我已經邀請了React團隊的成員來對本文檔的準確性進行review。
??Fiber是一項還在進行中的工作,在它完成前都很可能進行重改。所以本文檔也是如此,隨著時間很可能發生變化。歡迎任何的建議。
??我的目標是,在閱讀本文檔后,在Fiber完成的時候,順著它的實現你能更好的理解它。甚至最終回饋React(譯注:意思是fix bug,pr新特性,解決issue等等)。
準備??在繼續閱讀前,我強烈建議你確保自己對以下內容已經非常熟悉:
??React Components, Elements, and Instances - "組件"通常來說是一個范圍很大的術語。牢固的掌握這些術語是至關重要的。
??Reconciliation - 對React的協調/調度算法的一個高度概括。
??React基礎理論概念 - 對React中的一些概念模型的抽象描述,第一次讀的時候可能不太能體會。沒關系,以后終會明白的。
??React設計原則 - 請注意其中的scheduling這一小節,非常好的解釋了React Fiber。
回顧??如果你還沒準備好的話,請重新閱讀上面的"準備"一節。在我們探索之前,讓我們來了解幾個概念。
什么是協調(reconciliation)??reconciliation:是一種算法,React使用它去區分兩棵樹,從而決定到底哪一部分需要改變。
??update:數據的變化會導致渲染,通常這是setState的結果,最終會觸發重新渲染。
??React API的核心理念是思考/決定/調度怎樣去update,就好像它會導致整個app重新渲染一樣。它讓開發者能夠聲明式地去思考,而不用去擔心如何高效的將app從一個狀態過渡到另一個狀態(A到B,B到C,C再到A等等)。
??事實上,每次變化都重新渲染整個app的方式只能工作在非常小的app上。在現實世界真正的app中,這在性能上花費的代價太大了。React已經在這方面做了優化,在保持好性能的前提下創造出app重新渲染之后的樣子。絕大部分的優化都屬于reconciliation這個過程的一部分。
??Reconciliation是一個隱藏在被廣為熟知的稱作"virtual DOM"的背后的算法。概括起來就是:當你渲染一個React應用的時候,就產生了一棵描述這個應用的節點樹,并存儲在內存中。接下來這棵樹會被刷新,然后翻譯到具體的某個環境中。例如,在瀏覽器環境,它被翻譯成一系列的DOM操作。當app有更新的時候(通常是通過setState),一棵新的樹就產生了。這棵新樹會與之前的樹進行diff,然后計算出更新整個app需要哪些操作。
??雖然Fiber是一個對reconciler完全的重寫,但是React文檔中對核心算法的概括描述仍然是適用的。幾個關鍵點為:
不同的組件類型被假定為會產生本質上不同類型的樹。React不會嘗試對它們進行diff,而是完全地替換舊的樹。(譯注:如)
對列表(list,譯注:即組件元素組成的數組)的diff是采用key來進行的。Key應該是穩定的,可預測的,且唯一的。
Reconciliation vs rendering??DOM只是React能夠渲染的東西之一,除此之外,主要還有通過React Native產生的IOS和Android的原生控件。(這就是為什么說"virtual DOM"屬于用詞不當)
??React能支持這么多的渲染目標的是因為React本身的設計所導致的,協調(reconciliation)和渲染是兩個不同的,分離的階段。協調器(reconciler)做的是計算樹的哪部分在變化的工作,而渲染器(renderer)做的則是利用協調器產生的結果去更新我們的應用的工作。(譯注:即不同平臺/環境下去更新界面的手段/方式是不同的,所以不能一概而論,但是計算樹的差異的過程卻是通用的。)
??這種分離意味著React DOM以及React Native既能共享同一個由React提供的協調器的邏輯,又能夠利用它們各自的渲染器去完成渲染。
??Fiber重寫了協調器。它并不關心渲染,盡管渲染器需要相應作出一些改變(并且利用)這個新的算法的某些東西。
調度??調度(scheduling):是一個決定什么時候該做某個任務的過程。
??任務(work):任何需要執行的計算都屬于任務。任務通常是由一次更新所導致的。(如setState)
??React的設計原則這篇文檔在這一點上闡釋的非常不錯,所以我在這引用一小段:
在當前版本的實現中,React在一個工作輪回中遞歸地遍歷要更新的樹并且調用render函數。然而,在將來它也許會為了避免丟幀而延遲某些更新。
譯注:將來即指Fiber,幀是Fiber里引入的一個概念,因為用到了requestAnimationFrame。Fiber棧就是用來協調對幀的操作(Fiber棧也是Fiber里的概念,是一個對函數調用棧的模擬。)。延遲更新是相對遞歸遍歷而言的,即暫時中斷遞歸,轉去遍歷另外的節點。可參考演講視頻,或者觀察一下這個gif(有點大,20M)以及將幀劃分的圖片
這在React的設計中是一個很常見的課題。一些框架實現了"push"的方式,當新的數據可用的時候執行計算。然而,React堅持采用"pull"的方式,將計算延遲執行,直到有必要時才進行計算。
React并不是一個通用的數據處理框架。它是一個用于構建用戶接口的框架。我們認為它有自己獨特的定位,在一個應用中知道哪些相關的計算是目前所需要的,哪些是目前不需要的。
如果某些東西不可見(在屏幕外),我們可以延遲執行任何和這部分相關的邏輯。如果數據到達的頻率比幀刷新的頻率還要快,我們可以合并以及批處理這些更新。比起那些優先級不太高的任務(例如渲染從網絡獲取來的數據),我們可以優先考慮來自用戶接口的任務(例如,點擊一個按鈕觸發的動畫),從而避免丟幀。
幾個關鍵點在于:
在UI中,并不是每個更新都有必要立即展示給用戶。事實上,這樣做將會是很浪費的,會造成丟幀以及降低用戶體驗。
不同類型的更新具有不同的優先級 - 動畫過渡需要比更新數據更快。
譯注:完整的優先級可以參考源碼中的定義
基于push的方式需要app(程序員)去決定怎樣調度這些任務。基于pull的方式讓框架(React)變得智能,從而幫助我們做出這些抉擇。
??React目前并沒有非常好地利用調度,一次更新將會導致整個子樹立即被重新渲染。改進React的核心算法從而更好的利用調度是隱藏在Fiber背后的理念驅動。
??現在我們要準備深入Fiber的實現了。下一節會比我們到目前為止討論的要更有專業性一點。在你繼續閱讀前請確保之前的內容你基本了解了。
Fiber是什么??我們即將討論React Fiber的核心體系結構。Fiber比起應用開發者通常的認知而言,是一個更加的低得多的抽象層次。如果你發現自己很難去理解它,不要灰心。繼續嘗試,最后一定會撥開云霧見光明。(當你最后理解它的理解,請向我建議如何改進這一小節)
??我們開始吧~
??我們對Fiber已經確立的目標是,激活React,讓它具備調度的能力。具體地來說,我們需要能夠:
暫停及恢復任務。
賦予不同的任務不同的優先級。
重用之前已經完成的任務。
中止那些不再需要的任務。
??要想做到其中的任何一條,我們首先需要一種方式,把工作/任務分解成許許多多的小單元(units)。從某種意義上來說,那就是fiber。一個fiber代表了任務的單位。
??為了進一步理解,讓我們回到之前提到的把React組件當作數據的函數這一概念,通常表示為:
??v = f(d)
??由此可見,渲染一個React應用與在一個函數類調用另一個函數是類似的(譯注:一個組件的render函數里面會調用另一個組件的render函數)。這個類比在思考fiber的時候是很有用的。
??通常,計算機對一個程序的執行/調用情況的跟蹤的方式是通過調用棧(call stack)。當一個函數被執行的時候,一個新的棧幀(stack frame)被壓入棧中。那個棧幀就代表了在那個函數里被執行的任務。(譯注:聽著可能有點不順暢,不過無論什么語言,調試的時候觀察過call stack的同學應該都清楚)
??當我們處理UI的時候,問題在于如果一次有太多的任務要執行,將會導致動畫丟幀以及卡頓。更重要的是,那些任務當中的一部分也許是沒有必要執行的,如果新的一次更新對其中一部分進行了廢棄的話。這就是UI組件和函數分解之間有區別的地方,因為通常組件比函數有更多具體的需要關心的東西。
??較新的瀏覽器(以及React Native)實現了幫助解決這些具體問題的API:requestIdleCallback會讓一個低優先級的函數在空閑期被調用。而requestAnimationFrame會讓一個高優先級的函數在下一個動畫幀被調用。問題在于,為了使用這些API,你需要將渲染工作劃分為增量式的單元。如果你只依賴調用棧的話,那么直到調用棧為空之前它都會一直在工作。
??那么,如果我們能夠自定義調用棧的行為,對優化渲染UI來說是不是就更好了呢?如果我們能任意地中斷調用棧并且手動操作棧幀,是不是也會更好呢?
??這就是React Fiber的目標。Fiber是對于棧的重寫,特別是對于React組件來說。你可以把一個單一的fiber想象成一個虛擬的棧幀。
??重寫棧的優點是,你能夠在內存中保留棧幀(這個鏈接挺有趣的,值得一看),并且在任何時候通過任意方式執行。這對我們完成調度來說是至關重要的。
??除了調度外,手動地處理棧幀,也許能夠讓我們擁有一些潛在的特性,例如并發以及錯誤邊界處理。我們會在后面的小節討論這些。
Fiber的結構??注意:隨著我們對實現的細節關注得越具體,也許會發現更多的可能性。如果你發現錯誤或者太舊的信息,請給我們提pr。
??在具體的術語中,一個fiber是一個js對象,它包含著一個組件,以及這個組件的輸入及輸出。
??一個fiber與一個棧幀相對應,但同時也與一個組件的實例相對應。
??這里列出一些屬于fiber的重要的屬性(注意并沒有完全的列舉全):
type和key??fiber的type屬性和key屬性對React元素來講提供的是相同的功能。(事實上,當一個fiber從一個元素中被創建的時候,這兩個屬性都是復制過來的(譯注:可參考源碼))
??一個fiber的type描述了與它相對應的組件,對于函數或者類組件而言,type就是函數或者類組件本身(譯注:源碼中對type的描述為"與這個fiber相對應的函數/組件/模塊")。對于宿主組件而言(div,span等等),type就是字符串("div","span")。(譯注:這一點其實和之前的React是一樣的,沒有區別,如果你用react-devtools調試過的話應該會注意到)
??從概念上來講,type是一個函數(就像 v = f(d)),這個函數的執行被棧幀所追蹤。
??和type一起的key,被用在協調(reconciliation)過程中,決定這個fiber是否能被重用。(譯注:源碼中的描述為"這個child唯一的標識符")
child和sibling??這兩個屬性指向其它的fiber,描述一個fiber的遞歸樹結構。(譯注:源碼中的描述為"單向鏈表樹結構")
??child屬性對應的fiber是與一個組件的render方法的返回值相對應的。所以,在下面的例子中:
function Parent() { return}
??Parent的child屬性就與Child相對應。
??sibling屬性解釋了這樣的案例,即在render方法中返回多個子節點(一個在Fiber中的新特性)。(譯注:而且也可以返回一個字符串。相信都是大家期盼已久的,再也不用套一個div了。另外一個大的特性是error boundaries)
function Parent() { return [, ] }
??子fiber形成了一個單鏈表,單鏈表的頭節點是數組中的第一個元素。所以在上面的例子中,Parent的child屬性是Child1,Child1的sibling屬性是Child2。
??回到我們與函數的類比上,你可以把一個子fiber想象成一個尾調用函數。
return??return屬性的值也是一個fiber,指向處理完當前fiber之后的返回值。在概念上與棧幀的返回地址類似。
??如果一個fiber有多個子fiber,每一個子fiber的return屬性都執行父fiber。所以在我們上一節的例子中,Child1和Child2的return屬性的值都是Parent。
pendingProps和memoizedProps??從概念上來說,props就是一個函數的arguments。一個fiber的pendingProps在它最初被調用的時候就被設置了。memoizedProps在執行的結尾被設置。(譯注:應該就類似與對純函數進行cache)
??當將要到來的pendingProps和memoizedProps相等的時候,就標志著這個fiber以前的輸出能夠被重用了,這樣就能避免不必要的任務執行。
pendingWorkPriority??pendingWorkPriority的值代表了這個任務的優先級。ReactPriorityLevel列出了不同的優先級以及它們代表的含義。
??NoWork優先級的值是0,優先級數字越大表示優先級越低(即0是最高的優先級)。例如,你可以利用下面的函數去檢查一個fiber的優先級是否至少達到了某個指定的優先級。
function matchesPriority(fiber, priority) { return fiber.pendingWorkPriority !== 0 && fiber.pendingWorkPriority <= priority }
??這個函數僅僅只是為了說明使用,并不是真正的React Fiber代碼庫中的一部分。
??調度器使用priority屬性去搜索下一個要執行的任務單元。我們將在futrue一節討論這個算法。
alternate??flush:刷新一個fiber就是將它的輸出渲染到屏幕上。
??work-in-progress:代表一個還未完成的fiber,從概念上來說,類似于一個還未return的棧幀。
??在任何時候,一個組件的實例最多有2個fiber與它相關聯:當前的刷新后的fiber以及正在運行中(work-in-progress)的fiber。
??當前的fiber的備胎(alternate)就是正在運行的fiber,正在運行的fiber的備胎也是當前的fiber。(譯注:可參考源碼)
??一個fiber的備胎是用一個叫做cloneFiber的函數惰式創建的,而不是總是創建一個新的對象。如果fiber的備胎存在的話,cloneFiber會嘗試重用這個fiber的備胎,從而達到最小化分配內存的目的。
??雖然你應該把alternate屬性當作一種實現細節,但是在源碼中你會經常看到它,所以放到這里討論它是有價值的。
output??host component:代表一個React應用程序的葉子節點。不同的渲染環境下是不同的(例如,在瀏覽器應用里面,它們是div,span等等)。在JSX中,它們用小寫名來表示。(譯注:完整的分類可參考源碼)
??從概念上來說,一個fiber的輸出(output)是一個函數的返回值。
??每一個fiber最終都有一個輸出,但是只有在宿主環境的葉子節點中才會創建輸出。然后輸出被翻譯/轉移到真正的dom樹中。
??輸出就是最終傳給渲染器的東西,以便渲染器能夠在渲染環境中刷新,從而反映出那些變化。如何創建和更新輸出是渲染器的職責。
將來的可能??到目前為止我們就談這么多了。但是本文檔還遠遠沒有完成。未來我可能將描述一些在更新的生命周期中頻繁使用的算法。它們包括:
調度器是如何知道下一個要執行的單元是哪一個的?
在fiber樹中優先級是如何被追蹤和傳播的?
調度器怎么知道何時暫停和恢復某個任務?
任務是如何被刷新以及被標記為已經完成的?
副作用(如生命周期函數)是怎樣工作的?
協程(coroutine)是什么?它是怎樣被利用從而實現像context和layout這樣的特性的?
更多推薦React-Future
Fiber Principles: Contributing To Fiber
React 15.5 and 16 Umbrella
Fiber Simplify coroutines by making yields stateless
Fiber Umbrella for remaining features / bugs
React Perf Scenarios
Fiber Compute the Host Diff During Reconciliation
fiber-debugger
Why, What, and How of React Fiber with Dan Abramov and Andrew Clark
Pete Hunt: The Past, Present and Future of React
Dan Codes
另外之前收集過一些dan發在twitter上的東西,你可以進入鏈接然后ctrl+f搜索fiber。
------------------------------------------------------2017-4-16日更新---------------------------------------------------------------
That @reactiflux Q&A from @acdlite,關于這個更多的可以看discord里的討論
之前提到acdlite并非React項目組的成員,糾正下,準確度說應該是寫那篇文章的時候還不是,但是后面加入了React團隊。可參考這條tweet中的描述。另外其中也提到當時是作為一個旁觀者的角度去寫的那篇文章,經過在React項目組參與fiber的開發,文章里的很多東西也需要更新了,它后面會抽時間更新的,到時如果我沒忘的話應該也會更新翻譯的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82487.html
摘要:業界動態發布版本,同時發布了版本以及首個穩定版本的。程序人生如何用人類的方式進行二關于如何在中進行良好的溝通,避免陷入一些潛在的陷阱。技術周刊由小組出品,匯聚一周好文章,周刊原文。 業界動態 Angular 5.1 & More Now Available Angular發布5.1版本,同時發布了Angular CLI 1.6版本以及首個穩定版本的Angular Material。CL...
摘要:前端日報精選借助和緩存及離線開發中和走進之實現分析總是一知半解的中個常見的陷阱發布核心成員發布了免費的學習視頻中文譯的函數式編程是一種反模式掘金譯更好的表單設計每一頁,一件事實例研究掘金打印龍墨并不簡單結合實現簡單的加載動畫 2017-07-12 前端日報 精選 借助Service Worker和cacheStorage緩存及離線開發JavaScript中toString()和valu...
摘要:它的主體特征是增量渲染能夠將渲染工作分割成塊,并將其分散到多個幀中。實際上,這樣做可能會造成浪費,導致幀丟失并降低用戶體驗。當一個函數被執行時,一個新的堆棧框架被添加到堆棧中。該堆棧框表示由該函數執行的工作。 原文 react-fiber-architecture 介紹 React Fibre是React核心算法正在進行的重新實現。它是React團隊兩年多的研究成果。 React ...
摘要:在上面我們已經知道瀏覽器是一幀一幀執行的,在兩個執行幀之間,主線程通常會有一小段空閑時間,可以在這個空閑期調用空閑期回調,執行一些任務。另外由于這些堆棧是可以自己控制的,所以可以加入并發或者錯誤邊界等功能。 文章首發于個人博客 前言 2016 年都已經透露出來的概念,這都 9102 年了,我才開始寫 Fiber 的文章,表示慚愧呀。不過現在好的是關于 Fiber 的資料已經很豐富了,...
閱讀 1393·2021-11-22 15:11
閱讀 2838·2019-08-30 14:16
閱讀 2755·2019-08-29 15:21
閱讀 2914·2019-08-29 15:11
閱讀 2451·2019-08-29 13:19
閱讀 2985·2019-08-29 12:25
閱讀 417·2019-08-29 12:21
閱讀 2829·2019-08-29 11:03