- 支持使用不同的 UI 组件实现内部列表:React、MUI
- 支持:自动处理可变高度的 item、顶部加载更多、底部加载更多
- 支持:从特定位置开始渲染
结构
Scroller
列表的外层容器,高度为可视区域高度
- position: relative
- overflow-y: overlay
Viewport
列表的内层容器,高度为可视区域高度
- position: absolute
- top: 0px
Items
内容实现渲染的区域,高度为所有内容需要占用的高度(paddingTop + paddingBottom + height)
实现
urx 状态管理
https://virtuoso.dev/blog 作者在实现这个库时,抽象出的一个状态管理框架 – urx
- 最开始基于 redux:reducer 被反复调用,相互依赖,不好维护和测试
- 然后尝试 hook:并没有变的更好
- 接着采用 RxJS 重构:优化了渲染性能和测试,但是整个 rxjs 库太大了
- 于是作者自己封装了一个状态管理库: urx — 可以看作是一个精简版的 rxjs
urx 中的一些关键方法和概念
- stream 数据流:一个 stream 既能输出也能输入,每个 stream 可以有多个订阅者。流分为无状态和有状态两种:
- Stateless streams: 将输入的值发射到订阅者,不持有发布的数据
- Stateful streams: 将输入的值发射到订阅者,同时会持有最后输入的值
- system: 在 system 中做数据流的创建、组合等。一个 system 可以将其他 system 指定为依赖项,并且可以在依赖树中以单例的形式存在
- pipe: stream 中的值可以利用 pipe 管道结合一些操作符做转换和控制,比如 map、filter
- actions: urx Action 操作符作用于 stream 上
- publish: 在 stream 发布值
- subscribe: 订阅一个 stream
- react-urx: 提供了 systemToComponent 方法,将 urx systems 封装到 react UI 组件中,将 system 的输入、输出流转换到组件的输入、输出上:
|
|
-
还提供了几个 hook:
- useEmitterValue: 监听 stateful stream 的输出,并在 stream 有新的值时触发组件重新渲染
- useEmitter: 监听 stream 的输出,在 stream 有新的值时回调 callback,但不会触发重新渲染
- usePublisher: 返回一个函数,通过这个函数可以向 stream 中传递值
-
u.system 方法用于定义 systemSpec,u.system 调用返回 SystemSpec,SystemSpec 用于存储 system 的相关信息,每个 systemSpec 有 4 个属性
- id 唯一标识
- constructor 构造函数,构造函数的参数列表为它依赖的其他 systemSpec 对应的实例
- dependencies 它依赖的其他 systemSpec
- singleton 是否在依赖链中作为单例存在,默认为 true
|
|
- u.init 方法通过递归初始化一个 system 的依赖项来初始化 system
|
|
- u.connect 方法:连接两个流,发送到流 a 的值,会同时发送到流 b
渲染
列表的渲染从 systemToComponent 调用开始
|
|
combinedSystem
combinedSystem 依赖 listSystem 和 listComponentPropsSystem 中的所有 system
|
|
其他参数
- optional 注入到组件的参数,会作为 react 组件的 props
- menthods 暴露的方法,当调用某个方法时,会向方法名对应的 stream 传递参数值
- events 暴露的事件监听,订阅 event 名字对应的 stream
ListRoot 组件
在 ListRoot 中组装所有组件
|
|
-
Scroller 组件:负责构建外层的滚动容器
调用 useScrollTop 方法监听 div 的滚动事件,在滚动时将 scrollTop、scrollHeight、viewportHeight 写到 domIOSystem 的 scrollContainerState 流中
|
|
|
|
-
Viewport 组件:负责监听可视区域的 size 变动
size 变化后将新的高度值发射到 domIOSystem 的 viewportHeight 流
|
|
- Items 组件:列表容器,监听 listState 流
- 根据 listState 的 offsetTop 和 offsetBottom 改变列表容器的 paddingTop 和 paddingBottom
- 根据 listState.items 决定应该渲染哪些 items,所以核心就是 listState 的维护和更新
|
|
ListState
listState 在 listStateSystem 中维护,listState 是一个订阅了多个 stream 的 stream,当订阅的 stream 有新的值时就会触发生成 listState
|
|
|
|
- 开始渲染后 totalCount、visibleRange 还未设置, listStateSystem 返回空的 state
|
|
- listState 发生改变,触发 Items 组件渲染,渲染后发送 scrollContainerState 到 domIOSystem
|
|
|
|
- domIOSystem 收到 scrollContainerState,将 scrollTop 分发到 sizeRangeSystem 的 scrollTop stream
|
|
- Viewport 组件在检测到高度变化后,发送 viewportHeight 到 domIOSystem 的 viewportHeight stream,同时由于 sizeRangeSystem 订阅了 domIOSystem 的 viewportHeight,所以 sizeRangeSystem 也会收到 viewportHeight
|
|
- sizeRangeSystem 收到 scrollTop 和 viewportHeight 后,计算 visibleRange
|
|
- listStateSystem 收到 visibleRange,由于目前的 sizeTree 还是空的,所以返回初始 state,默认渲染第一个数据
|
|
- Items 组件渲染 items,导致 Items 组件的高度发生变化,触发 resize callback,从而获取 Items 所有 children 节点的高度存为 sizeRange,并发送到 sizeSystem 的 sizeRange stream
|
|
- sizeSystem 收到 sizeRange,计算 sizeState
|
|
sizeStateReducer
|
|
|
|
- listStateSystem 收到 sizeState,计算 listState
|
|
listState:
|
|
- Items 触发重新渲染,渲染新的 listState 中的 items,并修改 padding,就能在未渲染所有内容的情况下,预先确定整个滚动容器的高度 – scrollHeight
|
|
欢迎体验新产品, 抖音聊天