记录一下最近对单一输入源的理解。
最近在做的一个项目是这样的,一个音频播放网站,非 SPA,就像所有的音乐网站一样,所有页面的底部都有一个独立的音频播放器组件,在任意页面,如果有音频的播放按钮,点击这个按钮,播放器就会开始播放这个音频,并且显示这个音频的详细信息,比如 title, cover image, author。
之前的实现方案是,点击页面上的播放按钮后,会把当前此音频的所有信息通过 custom event dispatch 给完全独立的音频播放器,音频播放器通过 window.addEventListener()
监听这个 custom event,然后播放此新的音频并在界面上所示它的信息。
这样带来的问题是,在播放器组件,需要显示音频的 title/cover image/author,但在各个页面上,不一定显示这些信息,比如页面 A 只显示音频的 title,页面 B 只需要 cover image。但为了能在播放器组件上显示完整,在后端生成这个页面时不得不去获取音频的完整数据,虽然在页面上不需要显示出来。比如在访问页面 A,本来我只需要获取音频的 title,但实际我要去获取它的 title/cover image/author 等完整信息。
当页面多了以后,就会发生漏掉获取音频的某些属性,导致点击播放按钮后播放器崩溃,或者虽然不崩溃,但显示空白 (后者有时还不容发现)。
此时,音乐播放器面临输入源来自多个的问题,多个输入源可能导致状态/数据不一致。
然后我就想到了是不是可以把输入源单一化。
新的解决方案是这样的,在任意页面点击的播放按钮后,我只把当前音频的 id 通过 custom event 发送到音频播放器,然后音频播放器再通过音频的 id 访问 API,从 API 中拿到该音频的详细信息,然后显示和播放。
虽然多了一次网络请求,但它带来的好处不言而预,每个页面不再需要去获取多余的音频属性,音乐播放器只从 API 拿数据,能保证数据的完整性。
其实想想,redux 也可以算是一种单一输入源的方案。
在 Android/iOS app 的开发中,我们有时会把网络请求的结果缓存在本地数据库比如 SQLite 中,等下次打开时,先从本地数据库中加载缓存显示,然后再发网络请求,待网络请求返回后,先更新缓存,再将网络请求回来的数据直接显示到界面上。这时,界面上的数据就有两个输入源了,一个是缓存,一个是网络请求。更好的办法是,UI 永远只从缓存加载数据,而网络请求的结果只用来更新缓存。
单一读取源,多个写入源。