我的深度阅读插件,有一个用于控制插件行为的 Sidepanel 侧边栏。
这个侧边栏,是使用 preact + beer css 开发的。
Preact 是一个 3kb 的小型反应式框架,beer css 是一个小型的 css 框架,都是比较适合插件的选择。
选择 beer,是因为他的理念,我很喜欢,很简单,把 CSS 分成三个类型来进行组织,代码一下清晰了:
| SETTINGS | // The settings affects all document
|---------------|----|
| | |
| ELEMENTS | | // The elements are the components, widgets or tags
| | |
|---------------| |
| | |
| | |
| HELPERS |----| // The common helpers makes the elements more scalable
| |
| |
|---------------|
按照这种理念,组件应该这样写
<div class="article primary border max responsive"/>
class list 顺序很重要!!!!
选择 precat 不仅是因为它小而快,还是因为它的 signal 实现,是我最喜欢的,日常小项目,已经离不开了。
这里说的就是一个用 signal 封装 chrome storage 变化的工具函数,代码如下:
import { signal } from "@preact/signals";
const use_local_storage = async (key, safe_val) => {
debugger;
const val = signal(safe_val);
if (!key) return null;
else {
// Get stored settings.
const data = await chrome.storage.local.get(key);
if (data) {
val.value = data[key];
}
//subscribe settings.
chrome.storage.local.onChanged.addListener(changes => {
if (changes[key]) {
const { newValue } = changes[key];
val.value = newValue;
}
});
return val;
}
}
export {
use_local_storage
}
浏览器插件,因为安全的限制,几乎都只能执行在用户操作的上下文中,即插件不能也不应该在用户没有下达命令的情况下自己在后台搞事情。
因此大量代码都是在响应用户事件的异步上下文中,我倾向于所有的函数都定义成 async 函数。
这个函数的使用方法是,比如我要读取并监控存储在 chrome local storage 中的系统配置 settings.
需要使用到 sigal 这个反应式的状态数据。
那么只需要一行代码,这个程序就会一直响应 setting 的变化进行部分按需的重绘。
const settings = use_local_storage('settings', {});
const App = ({props}) => <div>
<p>{settings}</value>
</div?
render(<App { settings }>, $app);
如果项目变化,需要新增用户的 profile,那还是一行
const profile_signal = use_local_storage('profile', {})
signal 的强大之处在于,每一次使用 signal.value 都在创造出一个VDOM 和 state 之间的订阅关系,这使得代码可以做到,在组件树中传递 signal 是很 perfromance safe 的,signal 的变化只会引起订阅点的更新。
即只要你不明确读取状态,你就不会受到影响,这很 nice。
因此使用 context 以及 hook 的 场景大大减少了。
我现在的新个人项目,已经完全不会再使用各种钩子函数。react 框架的 context 包和 hooks 对我来说已经不存在了。
preact signal 的优势是,它并不是绑定于 preact 的,我自己就在大量的业务逻辑代码中使用。
按照这种思路,我们可以把插件中大量存在的content script 和 service worker 之间的消息,封装成 signal。
因为本质上消息的传递也是为了改变某个数据的状态,这样我们系统中大量的消息都被抽象成了 signal。
const chapter = use_runtime_message({topic:"/book/chapter"}, {});