Open yinguangyao opened 1 year ago
如果使用 redux 模式,会不会很繁琐
不是 redux 模式,更像 mobx 吧
---原始邮件--- 发件人: @.> 发送时间: 2024年6月4日(周二) 晚上8:26 收件人: @.>; 抄送: @.**@.>; 主题: Re: [yinguangyao/blog] 腾讯文档智能表格渲染层 Feature 设计 (Issue #83)
如果使用 redux 模式,会不会很繁琐
— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>
1. 前言
腾讯文档智能表格的界面是用 Canvas 进行绘制的,这部分称为 Canvas 渲染层。
出于性能的考虑,这里采用了双层 Canvas 的形式,将频繁变化的内容和不常变化的内容进行了分层。
image.png-29.5kB
如上图所示,表格部分如果没有编辑的话,一般情况下是不需要重绘的,而选区是容易频繁改变的部分。
也有一些竞品将选区用 DOM 来实现,这样也是一种分层,但对于全面拥抱 Canvas 的我们来说不是个很好的实践。
我们将背景不变的部分称为 BoardCanvas,和交互相关的 Canvas 称为 Feature Canvas。
今天主要简单来讲一下 Feature Canvas 这层的设计。
2. 插件化
首先,如何来定义 Feature 这个概念呢?在我们看来,所有和用户交互相关的都是 Feature,比如选区、选中态、hover 阴影、行列移动、智能填充等等。
这一层允许它频繁变化,因为绘制的内容比较有限,重绘的成本明显小于背景部分的绘制。
Kapture 2023-01-07 at 13.30.01.gif-380kB
这些 Feature 又该怎么去管理呢?需要有一套固定的模板来规范它们的代码组织。
因此,我们提倡使用插件化的形式来开发,每个 Feature 都是一个插件类,它拥有自己的生命周期,包括
bootstrap
、updated
、destroy
、addActivedEvents
、removeActivedEvents
等。有了这些钩子之后,每个 Feature 类就会比较固定且规范了。
假设我们需要实现一个功能,点击某个单元格,让这个单元格的背景高亮显示,该怎么做呢?
这里使用 Konva 这个 Canvas 库来简单写一个 Demo:
从上方的示例可以看到,一个 Feature 的开发非常简单,那么插件要怎么注册呢?
在一个统一的入口处,可以将需要注册的插件引入进来一次性注册。
这样一个简单的插件机制就已经完成了,管理起来也相当方便快捷。
3. 数据驱动
在交互中往往伴随着很多状态的产生,最初这些状态是维护在 Feature 中的,如果需要在外部访问状态或者修改 UI,就要使用
getFeature('xxx').yyy
的形式,这是一种不合理的设计。举个例子,我想要知道上面的高亮单元格是哪个,那么要怎么获取呢?
那如果想要复用这个 Feature 来高亮具体的单元格,要怎么做呢?
仔细观察这里面存在的几个问题:
如果开发过 React/Vue,都会想到这里需要做的就是实现一个 Model 层,专门存放这些中间状态。
其次要建立 Model 和 Feature 的关联,实现修改 Model 就会触发 Feature UI 更新的机制,这样就不需要从 Feature 上获取数据和修改 UI 了。
这里选用了 Mobx 来做状态管理,因为它可以很方便的实现我们想要的效果。
那么在 Feature 中如何使用呢?可以基于 Mobx 封装
observer
、watch
两个装饰器方便调用。至于
observer
和watch
的实现也很简单。watch
装饰器用于监听属性的变化,从而执行被装饰的方法。那这里为什么还需要
observer
呢?因为通过装饰器无法获取到类的实例,所以将$watchers
先挂载到原型上面,再通过 observer 拦截构造函数,进而去执行所有的$watchers
,这样就可以将挂载到类上的 Model 实例传进去。使用 Mobx 改造之后,避免了直接获取 Feature 内部的数据,或者调用 Feature 暴露的修改 UI 方法,让整体流程更加清晰直观了。
4. 总结
这里只是对渲染层 Feature Canvas 插件机制的一个小总结,基于 Mobx 我们可以实现很多东西,让整体架构更加清晰简洁。