前几天公司要做Demo展示, 业务的一部分使用RN Web做的, 其中有个需求是以树形结构展示用户各个项目的进度, 如图所示:
之前的同事是把这些条目按照顺序放到一个数组里(其实就是把一个树形结构压扁), 例如:
[父亲1,儿子11,孙子111,孙子112, 儿子12,孙子121,孙子122,孙子123,父亲2...]
然后把这个数组作为data传递给FlatList组件,依次渲染里面的component。
但问题是,数组里的条目有很多, 多层的嵌套关系导致元素有2000多条,当快速滚动FlatList时,尝尝出现白屏,需要等待1-2秒才能渲染成功。另外在父子层级之间折叠/展开的时候,由于要多次重新渲染,卡顿十分明显。另一个突出的问题是,由于数据结构是扁平的数组,很难区分父子元素,在处理折叠/展开的逻辑时, 需要很多额外的数据去做管理,逻辑不易处理。
优化的第一点:首先是把数据结构由扁平的数据转换为树形结构,利用children: []属性存储当前节点的子节点,从而更容易的表示出层级关系。转换函数如下:
listToTree
根据上面的例子,转换后的树形结构如下:
const
优化的第二点: 有个以上这个属性结构,我们就更容易的渲染出层级关系。但有一点我们要想到,就是父子的层级数量可能是不确定的,比如:
父亲 -> 儿子 -> 孙子
但也有可能是
父亲 -> 儿子 -> 孙子 -> 曾孙子
在我们的业务中,除了叶节点,也就是最后一个节点会使用TaskRow来渲染外,其他往上的父节点都会用LevelRow来渲染。为了适应结构层级的变化,我们需要一个组件能够嵌套渲染,也就是说它首先会渲染一个父节点(LevelRow),如果父节点有children,再以child为父节点,递归的使用该进行渲染,直到遇到了叶节点,则不再递归,而是渲染TaskRow。这样,我们即实现了这个树形结构的渲染过程,无论都多少层 都没关系。代码如下:
// @flow
用上述方法优化后,传入FlastList的data数据,从一个大小为1000多的数组,通过listToTree变成了只有6个根节点的数组(其他节点都以children属性的方式,从根节点开始嵌套引用)。而后,将这6个根节点依次用LevelWrapper渲染,即完成了从一个根节点出发,递归渲染至叶节点的过程。而每一层父子之间,通过LevelWrapper的showChildren 状态,可以很容易的控制父子之间的折叠/展开。
<
而每次的折叠/展开,改变的只是这一层下LevelWrapper组件的状态,其他位置的LevelWrapper并不会重新渲染,这就会大大提高FlatList的渲染效率,毕竟它不要每次都重新渲染1000多个组件。而在滚动FlatList的时候,它的数据源从一个1000长度的数组变为了只有6个元素的数组,也大大提高了FlatList的管理效率(虽然我对FlatList具体怎样管理也不是很清楚,但这样做确实是大大提高了渲染效果,白屏的问题也消失了)。
以上就是此次优化的主要内容。
补充: 除了优化FlatList外,还有一个功能就是能够根据关键字,搜索出需要的条目。根据文章开头的例子,比如用户搜索 ’孙子121‘,那么就应该展示出以下树形结构:
父亲1 -> 儿子12 -> 孙子121
这其实就是一个树形结构的搜索问题,我们业务中要求的结果就是:如果节点本身或者其子节点包含搜索内容,则该节点,该节点子节点,以及其父节点都要显示出来,换句话说就是只要树形结构中的任意一个节点满足要求, 那么这个节点所在的分支路径要完整的展示出来,效果如下:
当搜索’wall setout‘时,它的结果是一个叶子节点,则其父节点 L1-xxx 和 East Tower都要展示出来,这样用户才能知道这个结果处于哪些条目下.
当搜索’L1‘时,它是一个中间节点,则其父节点 East Tower和其余的子节点都要展示出来。
以上的要求,其实就是要根据搜索内容,对listToTree的结果做一次修剪,如果树形结构中的某个节点及其子节点都不满足条件的话,则将整个分支从树形结构中剪去。再次利用递归的知识,代码如下:
// 传入一组节点,检查节点及其子节点中是否有满足要求的,若有则返回true
以上就是本次工作的主要内容