【譯】React及React Fiber基本的設計理念

前言

本文主要是對收集到的一些官方或者其餘平臺的文章進行翻譯,中間可能穿插一些我的的理解,若有錯誤疏漏之處,還望批評指正。筆者並未研究過源碼,只是但願本文成爲那些inspire你的東西的一部分,從而在從此一塊兒去探討和研究React Fiber。html

注:絕大多數狀況下,如下的第一人稱不表明譯者,而是對應文章的做者,請注意區分。react

React basic

基礎的理論概念

  這篇文章是個人一次嘗試,但願可以形式化的介紹關於react自己的一些理念模型。目的在於基於演繹推理的方式,描述那些給咱們靈感讓咱們進行這樣的設計的源泉。ios

  固然,這裏的一些設想是具備爭議的,實際的設計也許也會有bug或者疏漏。可是,這也是一個好的開始讓咱們去形式化地談論這些。同時,若是你有更好的想法,也歡迎pr。如下讓咱們沿着這個思路,從簡單到複雜的去思考這一系列問題,沒必要擔憂,這裏沒有太多具體的框架細節。git

  實際的關於React的實現是充滿務實主義的,漸進式的,算法優化的,新老代碼交替的,各類調試工具以及任何你能想到的讓他變成更加有用的東西。固然,這些東西也像版本迭代同樣,它們的存在是短暫的,若是它們足夠有用,咱們就會不斷的更新他們。再次聲明,實際的實現是很是很是複雜的。程序員

轉換

  React最核心的前提是,UI僅僅是數據->數據的映射。相同的輸入意味着相同輸出。很是簡單的純函數。github

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)這一層面構建抽象,而且組合其它抽象。在我看來,組合就是將兩個甚至多個抽象變成一個新的抽象。segmentfault

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 => <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裏的代碼。最重要的是,若是咱們想將likesPerUserupdateUserLikes換成其餘的集合和函數的話,咱們必須再建立一個函數,如:

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,而是徹底地替換舊的樹。(譯註:如<Button> ->> <Menu />

  • 對列表(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 <Child />
  }

  Parent的child屬性就與Child相對應。

  sibling屬性解釋了這樣的案例,即在render方法中返回多個子節點(一個在Fiber中的新特性)。(譯註:並且也能夠返回一個字符串。相信都是你們期盼已久的,不再用套一個div了。另一個大的特性是error boundaries)

function Parent() {
    return [<Child1 />, <Child2 />]
  }

  子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應用程序的葉子節點。不一樣的渲染環境下是不一樣的(例如,在瀏覽器應用裏面,它們是divspan等等)。在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的開發,文章裏的不少東西也須要更新了,它後面會抽時間更新的,到時若是我沒忘的話應該也會更新翻譯的。

相關文章
相關標籤/搜索