一些需要注意的问题

在使用 Valtio 时,常常会出现以下问题,这些问题有的是对于 Valtio 的机制不理解造成的,有些则是因为 Valtio 的所受到的某些限制导致的。

重新渲染是根据组件中对于快照的访问粒度决定的

假定有以下 store 的定义。

const state = proxy({
  obj: {
    count: 0,
    text: [1, 2, 3],
  },
});

那么如果在一个 React 组件中仅仅使用 const snap = useSnapshot(state); 获取了这个 store 的快照,但是并没有访问快照中的任何值,那么这个组件将会在 state 这个 store 中任何内容发生变化的时候重新渲染。这个问题并不是 Valtio 的 bug,而是 Valtio 控制组件重新渲染的机制决定的,这个机制与组件中所使用到的快照的内容有关。

对于上面示例中的 store,在 React 组件中存在以下的重新渲染粒度控制。

  • 如果在组件中使用了 snap.obj.count,那么当 state.obj.count 发生变化时,组件会重新渲染。
  • 如果在组件中使用了 snap.obj,那么当 state.obj 中的任何内容发生变化时,包括 counttext,组件会重新渲染。
  • 如果在组件中使用 const snapObj = useSnapshot(state.obj); 获取的 obj 属性的快照,那么将只有当 state.obj 中的内容发生变化时,才会触发重新渲染。这一条规则与上一条规则的行为是一致的。

所以如果一个快照对象未通过任何属性进行访问,那么 Valtio 就会假定该对象整个对象都被使用了,因此这个对象中的任何更改都会触发重新渲染。

何时使用被代理的 store?何时使用快照?

store 的快照应该仅在 React 组件的渲染函数中使用,其他的位置都应该直接使用被代理的 store。

这里尤其要注意一下,在渲染函数中定义的回调函数(例如事件处理函数),虽然看起来是在组件的渲染函数中,但它们并没有。所以在回调函数中使用 store 的时候,不应该使用快照,而是要使用被代理的 store。

useEffect 的 deps 依赖值列表中使用快照值的时候,应该先将快照值解构出来,然后再使用解构出来的值。例如上一节中的示例里,如果 useEffect 要依赖快照中的 snap.obj.count 那么就应该将其解构出来:const { count } = snap.obj; 然后再使用 count