异步 store

Valtio 天然提供了对于异步 store 的支持。store 中的属性只要是 Promise 类型,那么在生成 store 的快照的时候,Valtio 就会异步处理这些值。而且这些异步的属性是可以使用 React 19 中提供的 use 来获取的,并且在读取这些异步属性的值的时候,如果组件使用了 Suspend 组件包裹,那么还可以有效的利用 Suspend 组件提供的功能。

例如以下是一个使用 fetch 异步获取数据的简单示例。

const store = proxy({
  book: fetch(bookUrl).then((book) => book.json()),
});

const Book = () => {
  const state = useSnapshot(store);

  return <div>{use(state.book).title}</div>;
};

const App = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Book />
    </Suspense>
  );
};

Caution

在使用异步操作的时候,useSnapshotuseTransition 搭配使用会出现问题,这是由于 useSnapshot 的设计影响的,要解决这个问题,可以使用 use-valtio 库提供的 useValtio Hook 来代替 useSnapshot。具体使用可以参考 Valtio 的使用一节。

刷新异步 store 中的属性

首先需要明确的一点是,虽然被称为异步 store,但只是这个 store 中的属性保存的是一个 Promise 类型的值,而不是一个方法。所以无论何时,在 store 的属性中保存的都应该是一个已经被 resolve 以后的 Promise 值。换句话说,对于这个值的刷新,并不在存储这个 Promise 值的属性本身,而是在其他的位置。

按照这样的思路,刷新这个异步 store 中的类型为 Promise 的属性,实际上就可以通过为其赋予一个新的 Promise 值来实现。z 最经典的示例就是将异步过程定义为一个 action,在需要的位置调用这个 action 即可。

export const booksStore = proxy({
  books: [] as Book[] | Promise<Book[]>,
});

export const fetchBooks = async () => {
  const response = await fetch("/api/books");
  const books = await response.json();
  booksStore.books = books;
};

在上面这个示例中,在任何位置只需要调用 fetchBooks 这个 action 函数,就可以立刻刷新 bookStore.books 中的 Promise 值,从而实现异步数据的更新。

Tip

对于异步 store 中内容的加载时机的处理,可以放置在父级组件的 useEffect 中,如果使用了 React Router 之类的路由框架,还可以放置在其 loader 函数中。