最近在做的一个项目中的筛选模块,首先该模块的筛选输入可能会需要考虑到至少深度为两层的树。每个节点我们姑且称为TreeNode
,而整个Tree
如下
root [
// 第0层,也就是所有可以筛选的项
{
key:foo,
// 第1层,奴属于该项的所有“可选项”或者“选项”
child:[
{
key:bar,
// 第二层,该项“选项”
child:[{}]
},
{ key:foo2 }
]
}
]
在阅读了部分naive-ui
的源码后,决定尝试使用 treeMate 来生成树的元数据,然后使用其提供的方法进行checked
和unchecked
操作。
#TreeMeta
创建元数据的方法比较简单。根据文档,只需要调用createTreeMate
即可。
import { createTreeMate } from "treemate";
createTreeMate([
{
key: "foo",
children: [
{
key: "bar",
},
],
},
]);
这样我们就获得了整个筛选组件树的元数据了。
#难题
但是,我们可以发现,在选项中,有些许内容并不是一开就有的,也就是说,选项内容是异步获取的,这包括
- 数据来源于后端的某个接口。
- 数据存在组件(data)中。
- 数据存在
vuex
,pinia
等状态管理工具中。
这时候就可能涉及到以下问题。
- 如何更新筛选组件中的内容(触发重新渲染问题)
- 对于一个封装好的组件而言,
createMateTree
的数据来源必然是通过props
传进来的,那当props
更新时,是否需要重新生成treeMate
? - 倘若
treeMate
重新生成,原来的勾选状态是否会丢失?
#问题一
这一问题可以通过一个巧妙方式解决。那就是只有当用户点开一个选项时,我们才去渲染筛选内容。这样做不但能保证在组件渲染时需要的数据已经完全加载,而且还能在一定程度上减少请求的数量,什么意思呢?
试想,如果有一个选项中数据是异步获得的,那么从逻辑上来说,若用户不需要进行这个选项的筛选,那么无论是对于用户,还是组件本身都不需要加载这一部分数据。这就很自然的导出一个结论,只有当用户点击需要异步获取的选项时,我们才去加载这一部分数据,能加载完成后再渲染组件内容。这就是 当用户点开一个选项时,我们才去渲染筛选内容
的意思。
那我们如何去实现这个事情呢?借助ES6
的Promise
,我们可以优雅的完成这个想法。
在TreeNode
中我们增加一个参数remote
,这个参数需要返回一个Promise
,且Promise
的返回值必须是一个符合规格的TreeNode
。这样,当我们需要打开这个筛选选项时,只需要判断一下当前节点是否有remote
,且这个remote
是否是要给Promise
即可。如果是,执行这个Promise
,成功后把数据(符合规格的TreeNode
)插入筛选的选项树中,打开筛选面板。任务就完成了。
#问题二
根据问题一可以知道,在用户点击选项后,数据可能获得了更新,这个原先的treeMate
必然无法覆盖到新的数据,我们需要再次调用createTreeMate
生成新的元数据。
#问题三
我们知道,在整个树中,每个选项需要拥有自己的,独立的,唯一的标识。它就是key
。关于key
我们可以有多种实践的方案。
- 主键自增(不是
- 使用
ES6
的新类型Symbol
- 使用命名空间
方案 1,方案 2 的优点就是容易书写。只需要几行代码就完事了。但随之而来又会带来新的问题。key
自增和使用Symbol
的方式在每次重新生成元数据后,已选的选项状态将会丢失。所以,我们需要一种更加稳定的,无论何时,何地重新生成元数据,同一几点的key
都必须相同的方法。这个方法便是使用命名空间。