React Fiber 树思想解决业务实际场景详解
明里人 人气:0熟悉 Fiber 树结构
我们知道,React 从 V16 版本开始采用 Fiber 树架构来实现渲染和更新机制。
Fiber
在 React 源码中可以看作是一个任务执行单元,每个 React Element 都会有一个与之对应的 Fiber 节点。
Fiber
节点的核心数据结构如下:
type Fiber = { type: any, //类型 return: Fiber, //父节点 child: Fiber, // 指向第一个子节点 sibling: Fiber, // 指向下一个弟弟 }
其中,以下三个属性可以构成 Fiber 树:
return
表示父 Fiber 节点(顶级元素没有 return 指针)sibling
表示下一个兄弟 Fiber 节点(如果没有下一个兄弟节点,也就没有这个指针)child
表示第一个子 Fiber 节点(如果没有第一个子节点,也就没有这个指针)。
举个例子,假如我们的组件结构如下:
function App() { return ( <div> 名称: <span>明里人</span> </div> ) }
对应的 Fiber 树结构如下:
通过 Fiber 树结构我们可以很方便的查找一个节点的上级、下级和同级。
接下来我们一起来看看实际的业务场景。
业务场景
有时我们会去实现一些任务类的需求,任务自身存在 status 状态,比如进行中、逾期、完成状态,每个任务都可以通过完成按钮被完成。
假设现在有一个「任务流」作为第一级数据出现,它的子集由一个或多个「任务组」组成,作为第二级数据出现,任务组的子集由一个或多个「任务」组成,作为第三级数据出现。
现有需求如下:
第三级的「任务」在完成时更新自身状态,但要考虑同步上级节点的状态:
- 如果当前任务所在的任务组下所有任务都已完成,更新当前「任务组」状态为完成;
- 如果所有任务组及任务都已完成,更新「任务流」状态为完成。
当「任务流」处于完成状态时,UI 的体现可能如下:
思路:
我们要在每个任务完成后,判断同级任务是否都已完成,若都已完成,则将父级(任务组)状态更新为完成。
如果我们将现有的菜单树结构改造为 Fiber 树结构,通过 return
可以很方便的找到父节点,然后通过 child 和 sibling
可以很方便的查找任务组下的每一个任务,决定是否更新任务组状态。
代码实现如下:
// 1. taskFlowData 任务流数据 // 2. currentTaskData 当前完成的任务数据 export const getFinishedStatus = (task) => task.status === 1; // 1 代表完成 export const setFinishedStatus = (task) => task.status = 1; // 首先,将当前任务的状态更新 setFinishedStatus(currentTaskData); // 第一步,将现有数据转换为 Fiber 树结构 const createNode = (value, parent) => ( { value, return: parent, child: null, sibling: null } ); // 1-1. 创建 root fiber 根节点,即 任务流 const rootNode = createNode(taskFlowData, undefined); let currentNode = rootNode, currentTaskNode = null; // 1-2. child 存放的是任务组,为第二级数据创建 Fiber 节点 currentNode.value.child.forEach((taskGroup, taskGroupIndex) => { const node = createNode(taskGroup, rootNode); // 1-3. 建立关系 taskGroupIndex === 0 ? (currentNode.child = node) : (currentNode.sibling = node); // 1-4. 为第三级任务创建 Fiber 节点 taskGroup.child.forEach((task, taskIndex) => { const childNode = createNode(task, node); taskIndex === 0 ? (node.child = childNode) : (currentNode.sibling = childNode); currentNode = childNode; // 1-5. 记录当前任务对应的 Fiber 节点 if (task.id === currentTaskData.id) currentTaskNode = currentNode; }); currentNode = node; }); // 第二步,根据 Fiber 树结构,来查找并更新状态 let parentNode = currentTaskNode.return; while (parentNode) { let isFinished = true; // 2-1. 找到第一个任务 let workInprgress = parentNode.child; // 2-2. 从第一个任务开始,依次查看每个任务的状态 while (workInprgress) { // 2-3. 如果查找的当前任务处于未完成状态,无需更新父级状态 if (!getFinishedStatus(workInprgress.value)) { isFinished = false; break; } workInprgress = workInprgress.sibling; } // 2-4. 更新任务组状态,再向上查找,确定是否更新任务流状态 if (isFinished) { setFinishedStatus(parentNode.value); parentNode = parentNode.return; } else { // 2-5. 任务组状态不需要更新,直接结束 break; } }
加载全部内容