React FiberNode 在业务中的具体应用

React

《前端工程师职业生涯中的三个核心问题》一文中,曾提到过「工具」的重要性。今天,就以去年开发的一个实际应用到工作中的浏览器插件为例,简单阐述下在这一方面的具体实践。

1. 背景

在工作场景中,报表是一种常见的业务形态。老板需要通过报表了解公司的经营状态,财务需要通过报表掌握收支明细,产品运营需要通过报表了解用户数据。从抽象层面上来看,种种报表均是数据与展示形式组合的产物。其中,

概念解释
数据顾名思义,也即后端返回的结构化数据
展示形式则需要根据数据的不同、使用报表的目的不同而差异化的表现为常见的表格、饼图、折线图、柱状图等形式

也即:

报表=动态数据(后端)+图表多样展示(前端)报表 = 动态数据(后端) + 图表多样展示(前端)

如下图:

报表示例文件

从实现的角度来看,客户端将用户在界面设置的筛选条件,格式化成特定查询语句后,交由后端做具体的数据处理,在数据返回到客户端后,客户端再将结构化数据按照展示需要进行特定格式化,从而展示在用户面前。

data-flow

可见,在客户端侧,技术模型并不复杂。客户端所面临的复杂度来源于:

  • 如何将通用业务流程模型抽象化
  • 报表种类繁多,导致页面数量庞大
  • 前后端交互传输的数据多、格式各异、数据处理流程个性化

一旦数量上去之后,比如一百多张报表,且随着时间的流逝与人员的更迭,当前维护人员往往并不清楚前后端数据交换协议的细节以及格式化规则,这对于前端页面的后期维护成本而言,可以说是陡然而升。 排查因传参或取值类型的错误,花费了大量人力成本。因此,如果有一个工具,能够一眼看到前端发送给后端的数据、以及页面渲染时又是取的什么值,在一定程度上便能够降低问题排查的成本。

2. 目标

明确了当前面临的问题,下一步则需要确定一个具体的目标。对于上述阐述的小问题,目标是非常明确的:能够提供一个工具或平台,帮助开发人员快速查看当前报表的具体请求参数和渲染取值

3. 方案

首先最容易想到的便是侵入式地给页面组件添加特定标识或按照某种约定命名,然后外部再去读取相应的信息。 但此种做法侵入性太强,对本就 BUG 频出的业务页面及脆弱的开发人员而言,都无疑是雪上加霜。因此,无代码侵入,变成了方案的首要前提。

使用 React 的同学一定熟悉 React Devtools,在 Component 面板,我们能够清晰的看到页面的组件层级及每个组件对应的 props。这便是灵感的来源,心想,在 DOMReact Compoent之间一定存在着某种连接,才能够做到 DOMComponent 以及 ComponentDOM 的映射。如果能够获取到 Component,也就获取到了有关组件的所有信息。且是代码无侵入的。

data-flow

随后,在 React 的源码中看到使用随机字符串生成key的操作。打开控制台,果然,我们找到了这种映射关系

fiber

如上图所示,每一个React DOM元素都包含有以__react(视ReactDOM版本不同而不同)开头的属性,其中存储着当前DOMVirtualDOM也即FiberNode信息。根据FiberNode的定义,其中memoizedProps中便存储着渲染当前元素所使用的props

有了这层映射关系,在外层,就不需要关心业务以及React的处理过程,只关注最后生成的DOM结果,因为结果的对错,是我们最关心的问题。然后,剩下的事情就是处理具体的业务场景,顺水推舟了。

比如:

  • 最终实现无侵入,还是通过浏览器扩展实现,但浏览器扩展与页面不在一个执行环境中,无法读取到DOM属性,此时我们选择通过扩展,向页面注入JS脚本,通过事件机制传递数据
  • React 不同版本稍微有些许差异的处理
  • 在获取表格头时,表头是嵌套分组的,需要处理表头嵌套问题
  • 执行时机问题,每次数据变化时需要重新获取React Component的最新实例,以获取最新的值。在页面中,引起数据变化的是请求,因此,在扩展中监听网络请求,从而做到数据的同步

最终效果如下:

example

此处只阐述了,具体思路,有了这一方面的思路与具体实践后,该思路也运用在了目前正在主导的自动化测试中。后续有时间再一一道来。

如有任何问题或建议,欢迎在issues中交流讨论