常见问题

❓ 如何在 Reactive 中存储未代理状态,以保持使用体验一致性

您可以使用 ref 来实现这一点。当您希望在代理中嵌套一个不被内部代理包裹的对象时(比如存储 DOM 元素、File 对象等非结构化数据),这非常有用,但请注意,它的变化不会被追踪。更多详情和注意事项请参阅 ref

❓ 当状态值传递给 <input /> 元素时,输入过程可能会被打断

默认情况下,状态变更时,会异步地通知变更,来实现合并更新(即批处理)的效果,以优化渲染。

如果您想禁用它(例如被 <input /> 元素使用时,可能与输入法的 Composition 事件 冲突),您可以在使用 store.useSnapshot 时设置 sync 选项为 true 来同步地通知变更,以避免这个问题。

1const store = create({ inputValue: '' })
2
3const updateInputValue = (value: string) => {
4  store.mutate.inputValue = value
5}
6
7function App() {
8  const inputValue = store.useSnapshot((s) => s.inputValue, { sync: true })
9
10  return (
11    <input
12      value={inputValue}
13      onChange={(e) => {
14        updateInputValue(e.target.value)
15      }}
16    />
17  )
18}

❓ 当操作超大数据集(通常读千万次以上)时,有较为明显的卡顿

在几乎绝大多数使用场景中,您可能不会遇到性能瓶颈。然而,当在处理超大数据集(千万级别以上读操作次数)时,性能问题可能会成为一个难题。这主要是由于使用 Proxy 时,每次数据访问都会触发代理的 Getter 方法,从而在进行大量的读操作时产生显著的性能开销,为避免超大数据集下的性能问题,可以考虑以下解决策略:

  • 使用 useState: 使用 useState 等钩子单独管理不需要响应式特性的大数据集。
  • 使用 ref 包裹: 使用 ref 包裹大数据集以避免被 Proxy 代理。

你可以通过以下代码在控制台中直观地感受到这种性能差异:

const obj = { name: 'Reactive' };
const proxiedObj = new Proxy(obj, {});

console.time('Normal Object Get');
for(let i = 0; i < 100_000_000; i++) obj.name;
console.timeEnd('Normal Object Get'); // ~50ms, Chrome 131, MacBook Pro (M1 Pro + 16G)

console.time('Proxied Object Get');
for(let i = 0; i < 100_000_000; i++) proxiedObj.name;
console.timeEnd('Proxied Object Get'); // ~1000ms, Chrome 131, MacBook Pro (M1 Pro + 16G)