【译】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的开发,文章里的不少东西也须要更新了,它后面会抽时间更新的,到时若是我没忘的话应该也会更新翻译的。

相关文章
相关标签/搜索