讲这个问题之前我们要先定义什么叫 UI
,一个朴素的想法是把 UI 看成一个简单的 MVC 结构——数据 流向
视图,以及一系列操作可以修改数据。以这个 React 组件为例:
function App() {
let [count, setCount] = useState(0);
// ^ 数据
return <button onClick={() => setCount(count + 1)}>{count}</button>; // 视图
// ^ 有可能引发数据变化的操作
}
其他框架(Vue、Svelte)我就不赘述了,这里的基本思想是:数据和视图有关,而且必须是响应式的(能够监听到数据变动从而通知 UI 更新);操作有可能和数据有关,也可以无关。
因此我们的核心库给出了这样的接口:
let app = await createFastboard({ ...config });
app.writable; // { value: true, subscribe/reaction, set? }
value
直接取到当前值。
subscribe/reaction
监听值变化,回调参数是新的值。区别是 subscribe 会当场执行一次,reaction 不会。
返回一个取消监听的函数。
let dispose = app.writable.subscribe(value => {
console.log("writable:", value);
}); // writable: true
app.writable.reaction(value => {
console.log("writable2:", value);
}); // not print anything
app.writable.set(false); // writable: false, writable2: false
dispose();
app.writable.set(true); // writable2: true
set
(可能存在)修改值,只有某些值可以被修改,注意这个不一定会同步更新到 value 上,以 value 上的值为准。
我们可以利用 React Hooks,将其做成响应式的值:
import { useState, useEffect } from "react";
function useAppWritable(app) {
const [value, setValue] = useState(app.writable.value);
useEffect(() => app.writable.subscribe(setValue), [app]);
// 这里利用了一个"巧合":subscribe 返回的 dispose 函数恰好可以被 useEffect 自动调用,
// 在退出组件时清除监听。
return value;
}
利用 Vue3 的响应式接口:
警告:Fastboard 内部有一些值本身是封装成 Proxy 的,此时在 vue 里二次嵌套 proxy 很容易发生问题,建议不要将 app 放到 data
或者 ref
reactive
里,一定要放的话建议使用 shallowRef
。
import { ref, readonly, computed, onUnmounted } from "vue";
function useAppWritable(app) {
const writable = ref(app.writable.value);
const dispose = app.writable.reaction(value => {
writable.value = value;
});
onUnmounted(dispose);
// 不希望外部调用 "writable.value ="
return readonly(writable);
// 如果希望封装成可变的值,可以用 computed,
return computed({
get: () => writable.value,
set: value => {
app.writable.set(value);
// 注意!这里不是 writable.value = value,
// 我们需要修改到真实的数据,而不是封装的
},
});
}
我们的接口恰好
也是 Svelte Store 的接口,因此可以直接使用:
<script>
let writable = app.writable;
</script>
<div>{$writable ? '可写' : '不可写'}</div>