The `$state` 符文允许您创建 *响应式状态*,这意味着当它改变时,您的 UI *会响应*。 ```svelte ``` 与您可能遇到的其他框架不同,没有用于与状态交互的 API —— `count` 只是一个数字,而不是一个对象或函数,您可以像更新任何其他变量一样更新它。 深层政府 如果使用 `$state` 与数组或简单对象一起,结果将是一个深度响应的 *状态代理*。 [代理](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 允许 Svelte 在读取或写入属性时运行代码,包括通过 `array.push(...)` 等方法,从而触发细粒度更新。 状态递归代理,直到 Svelte 找到除了数组或简单对象(如类或使用`Object.create`创建的对象)之外的内容。在这种情况下... ```js let todos = $state([ { done: false, text: 'add more todos' } ]); ``` 修改单个待办事项的属性将触发依赖于该特定属性的 UI 中任何内容的更新: ```js let todos = [{ done: false, text: 'add more todos' }]; // ---cut--- todos[0].done = !todos[0].done; ``` 如果您向数组中推送一个新的对象,它也将被代理: ```js let todos = [{ done: false, text: 'add more todos' }]; // ---cut--- todos.push({ done: false, text: 'eat lunch' }); ``` > \[!注意\] 当您更新代理属性时,原始对象*不会被*修改。如果您需要在状态代理中使用自己的代理处理器,[您应该在将对象*包装*在`$state`](https://svelte.dev/playground/hello-world?version=latest#H4sIAAAAAAAACpWR3WoDIRCFX2UqhWyIJL3erAulL9C7XnQLMe5ksbUqOpsfln33YuyGFNJC8UKdc2bOhw7Myk9kJXsJ0nttO9jcR5KEG9AWJDwHdzwxznbaYGTl68Do5JM_FRifuh-9X8Y9Gkq1rYx4q66cJbQUWcmqqIL2VDe2IYMEbvuOikBADi-GJDSkXG-phId0G-frye2DO2psQYDFQ0Ys8gQO350dUkEydEg82T0GOs0nsSG9g2IqgxACZueo2ZUlpdvoDC6N64qsg1QKY8T2bpZp8gpIfbCQ85Zn50Ud82HkeY83uDjspenxv3jXcSDyjPWf9L1vJf0GH666J-jLu1ery4dV257IWXBWGa0-xFDMQdTTn2ScxWKsn86ROsLwQxqrVR5QM84Ij8TKFD2-cUZSm4O2LSt30kQcvwCgCmfZnAIAAA==)之后进行包装。 请注意,如果您解构一个响应式值,引用将不会是响应式的——就像在常规 JavaScript 中一样,它们在解构点被评估: ```js let todos = [{ done: false, text: 'add more todos' }]; // ---cut--- let { done, text } = todos[0]; // this will not affect the value of `done` todos[0].done = !todos[0].done; ``` 类 类实例不会被代理。相反,您可以在类字段(无论是公共的还是私有的)中使用 `$state`,或者在 `constructor` 内部立即将属性赋值为第一个赋值: ```js // @errors: 7006 2554 class Todo { done = $state(false); constructor(text) { this.text = $state(text); } reset() { this.text = ''; this.done = false; } } ``` > \[注意\] 编译器将 `done` 和 `text` 转换为类原型的 `get`/`set` 方法,引用私有字段。这意味着属性不可枚举。 在调用 JavaScript 中的方法时,[`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)的值很重要。这不会工作,因为`this`在`reset`方法中将是` ``` 您可以使用内联函数... ```svelte ``` ...或在使用类定义时使用箭头函数: ```js // @errors: 7006 2554 class Todo { done = $state(false); constructor(text) { this.text = $state(text); } +++reset = () => {+++ this.text = ''; this.done = false; } } ``` 内建类 Svelte 提供了内置类如`Set`、`Map`、`Date`和`URL`的反应式实现,可以从[`svelte/reactivity`](svelte-reactivity)导入。 ## `$state.raw` 在您不希望对象和数组深度响应式的情况下,可以使用`$state.raw`。 状态声明为 `$state.raw` 无法修改;它只能 *重新分配*。换句话说,如果您想更新它,而不是分配给对象的属性或使用数组方法如 `push`,请完全替换对象或数组: ```js let person = $state.raw({ name: 'Heraclitus', age: 49 }); // this will have no effect person.age += 1; // this will work, because we're creating a new person person = { name: 'Heraclitus', age: 50 }; ``` 这可以改善大型数组和对象(您本来就不打算修改的)的性能,因为它避免了使它们变为响应式的成本。请注意,原始状态可以*包含*响应式状态(例如,响应式对象的原始数组)。 与`$state`一样,您可以使用`$state.raw`声明类字段。 ## `$state.snapshot` 要获取一个深度响应式代理`$state`的静态快照,请使用`$state.snapshot`: ```svelte ``` 这是一个方便的情况,当你想要将一些状态传递给一个不期望代理的外部库或 API 时,例如 `structuredClone`。 传入状态到函数 JavaScript 是一种 *按值传递* 的语言——当你调用一个函数时,参数是 *值* 而不是 *变量*。换句话说: ```js /// file: index.js // @filename: index.js // ---cut--- /** * @param {number} a * @param {number} b */ function add(a, b) { return a + b; } let a = 1; let b = 2; let total = add(a, b); console.log(total); // 3 a = 3; b = 4; console.log(total); // still 3! ``` 如果`add`想要访问`a`和`b`的*当前*值,并返回当前的`总计`值,您需要使用函数: ```js /// file: index.js // @filename: index.js // ---cut--- /** * @param {() => number} getA * @param {() => number} getB */ function add(+++getA, getB+++) { return +++() => getA() + getB()+++; } let a = 1; let b = 2; let total = add+++(() => a, () => b)+++; console.log(+++total()+++); // 3 a = 3; b = 4; console.log(+++total()+++); // 7 ``` 状态在 Svelte 中并无不同——当你引用使用`$state`符声明的某个内容时... ```js let a = +++$state(1)+++; let b = +++$state(2)+++; ``` ...您正在访问其*当前值*。 请注意,“函数”概念很广泛——它包括代理属性以及 [`get`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/[`set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) 属性... ```js /// file: index.js // @filename: index.js // ---cut--- /** * @param {{ a: number, b: number }} input */ function add(input) { return { get value() { return input.a + input.b; } }; } let input = $state({ a: 1, b: 2 }); let total = add(input); console.log(total.value); // 3 input.a = 3; input.b = 4; console.log(total.value); // 7 ``` ...尽管如果你发现自己正在编写那样的代码,考虑使用[类](#Classes)代替。 模块间传递状态 您可以在`.svelte.js`和`.svelte.ts`文件中声明状态,但只有在该状态未被直接重新赋值时才能*导出*该状态。换句话说,您不能这样做: ```js /// file: state.svelte.js export let count = $state(0); export function increment() { count += 1; } ``` 这是因为对`count`的每一个引用都会被 Svelte 编译器转换——上面的代码大致等同于以下内容: ```js /// file: state.svelte.js (compiler output) // @filename: index.ts interface Signal { value: T; } interface Svelte { state(value?: T): Signal; get(source: Signal): T; set(source: Signal, value: T): void; } declare const $: Svelte; // ---cut--- export let count = $.state(0); export function increment() { $.set(count, $.get(count) + 1); } ``` > \[!注意\] 您可以通过点击“JS 输出”标签在 [沙盒](/playground) 中查看 Svelte 生成的代码。 由于编译器一次只处理一个文件,如果另一个文件导入 `count`,Svelte 就不知道需要将每个引用包裹在 `$.get` 和 `$.set` 中: ```js // @filename: state.svelte.js export let count = 0; // @filename: index.js // ---cut--- import { count } from './state.svelte.js'; console.log(typeof count); // 'object', not 'number' ``` 这使您在模块之间共享状态时有两个选项——要么不重新分配它... ```js // This is allowed — since we're updating // `counter.count` rather than `counter`, // Svelte doesn't wrap it in `$.state` export const counter = $state({ count: 0 }); export function increment() { counter.count += 1; } ``` ...或不要直接导出: ```js let count = $state(0); export function getCount() { return count; } export function increment() { count += 1; } ```