创建一个 store

创建一个 store 使用的是 Valtio 中提供的 proxy 函数。这个函数接受一个普通的对象作为参数,并返回一个代理对象。这个代理对象可以像普通对象一样被读取和修改,但所有的修改都会被自动跟踪。

以下是一个最简单的 store 示例。

import { proxy } from "valtio";

const store = proxy({
  count: 0,
});

甚至可以创建一个嵌套的 store。

const personStore = proxy({ name: "John", role: "user" });
const authenticationStore = proxy({ status: "logged-in", user: personStore });

注意在使用 proxy 创建 store 的时候不是什么内容都可以被代理的,通常可以被序列化的类型都可以被代理,比如字符串、数字、布尔值、数组、对象等。例如以下内容就不能被代理,因为其中的内容是无法被序列化的。

const unableProxied = proxy({
  chart: d3.select("#chart"),
  component: React.createElement("div"),
  map: new Map(),
  storage: localStorage,
});

Tip

一个类的实例是可以被代理的。

Valtio 在创建一个对象的代理的时候,会自动将其中嵌套的列表、对象等也都进行代理。所以上面嵌套定义的 store 甚至可以简化成以下形式。

const authenticationStore = proxy({
  status: "logged-in",
  user: { name: 'John', role: "user },
});

Caution

注意这个简化的版本在实际使用的时候与之前使用组合方式定义 store 所产生的效果会有区别。通过组合定义的嵌套 store 在使用的时候会提供更多的功能。

在被代理对象中声明无需代理的属性

Valtio 虽然可以自动代理一个对象中的所有嵌套内容,但是也提供了一种方式来声明一些属性不需要被代理。这可以通过在定义 store 的时候,使用 ref 来实现。例如:

import { proxy, ref } from "valtio";

const store = proxy({
  page: 1,
  total: 0,
  books: ref([]),
});

在这个例子中,books 属性被使用 ref 包装了,这意味着 books 属性不会被自动代理。相反,它将作为一个普通的 JavaScript 数组来处理。例如现在调用 store.books.push({ id: 1, title: "Book 1" }) 是不会触发任何更新的。但是需要注意的是,如果对 books 重新赋值是不可以的,这会破坏 ref的作用,例如 store.books = []

Caution

在仅有一个属性的 store 中,不要将这个属性定义使用 ref 包装,这会导致 Valtio 的代理变得毫无意义。