$state
Руна $state
позволяет вам создавать реактивное состояние, что означает, что ваш пользовательский интерфейс реагирует на изменения.
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
нажатий: {count}
</button>
В отличие от других фреймворков, с которыми вы могли столкнуться, нет API для взаимодействия с состоянием — count
просто число, а не объект или функция, и вы можете обновлять его так же, как обновляете любую другую переменную.
Глубокое состояние
Если $state
используется с массивом или простым объектом, результатом будет глубоко реактивный прокси состояния. Объекты Прокси позволяют Svelte выполнять код при чтении или записи свойств, включая методы, такие как array.push(...)
, что вызывает детализированные обновления.
Состояние проксируется рекурсивно, пока Svelte не найдет что-то, кроме массива или простого объекта. В таком случае…
let todos = $state([
{
done: false,
text: 'добавить задачу'
}
]);
…изменение свойства отдельной задачи вызовет обновление всего в вашем пользовательском интерфейсе, что зависит от этого конкретного свойства:
todos[0].done = !todos[0].done;
Если вы добавите новый объект в массив, он также будет проксирован:
todos.push({
done: false,
text: 'пообедать'
});
Обратите внимание, что если вы деструктурируете реактивное значение, ссылки не являются реактивными — как и в обычном JavaScript, они анализируются в момент деструктуризации:
let { done, text } = todos[0];
// это не повлияет на значение `done`
todos[0].done = !todos[0].done;
Классы
Вы также можете использовать $state
в полях класса (как публичных, так и приватных):
class Todo {
done = $state(false);
text = $state();
constructor(text) {
this.text = text;
}
reset() {
this.text = '';
this.done = false;
}
}
При вызове методов в JavaScript значение this
имеет значение. Это не сработает, потому что this
внутри метода reset
будет ссылаться на <button>
, а не на Todo
:
<button onclick={todo.reset}>
reset
</button>
Вы можете использовать либо встроенную функцию…
<button onclick={() => todo.reset()}>
reset
</button>
…либо стрелочную функцию в определении класса:
class Todo {
done = $state(false);
text = $state();
constructor(text) {
this.text = text;
}
reset = () => {
this.text = '';
this.done = false;
}
}
$state.raw
В случаях, когда вы не хотите, чтобы объекты и массивы были глубоко реактивными, вы можете использовать $state.raw
.
Состояние, объявленное с помощью $state.raw
, не может быть мутировано; его можно только переопределить. Другими словами, вместо того чтобы присваивать значение свойству объекта или использовать метод массива, такой как push
, замените объект или массив целиком, если хотите его обновить:
let person = $state.raw({
name: 'Heraclitus',
age: 49
});
// это не окажет никакого эффекта
person.age += 1;
// это сработает, потому что мы создаем новый объект
person = {
name: 'Heraclitus',
age: 50
};
Это может улучшить производительность при работе с большими массивами и объектами, которые вы не планировали мутировать, поскольку это позволяет избежать затрат на их реактивность. Обратите внимание, что исходное состояние может содержать реактивное состояние (например, исходный массив реактивных объектов).
$state.snapshot
Чтобы сделать статический снимок глубоко реактивного прокси $state
, используйте $state.snapshot
:
<script>
let counter = $state({ count: 0 });
function onclick() {
// Будет выведено `{ count: ... }`, а не `Proxy { ... }`
console.log($state.snapshot(counter));
}
</script>
Это удобно, когда вы хотите передать состояние во внешнюю библиотеку или API, которые не ожидают прокси, такие как structuredClone
.
Передача состояния в функции
JavaScript — это язык с передачей по значению — когда вы вызываете функцию, аргументы являются значениями, а не переменными. Другими словами:
// index.js
/**
* @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); // всё ещё 3!
// index.ts
function add(a: number, b: number) {
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); // всё ещё 3!
Если add
хочет получить доступ к текущим значениям a
и b
, а также вернуть текущее значение total
, вам нужно использовать функции вместо этого:
// index.js
/**
* @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
// index.ts
function add(getA: () => number, getB: () => number) {
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
…
let a = $state(1);
let b = $state(2);
…вы получаете его текущее значение.
Обратите внимание, что «функции» — это широкое понятие — оно охватывает свойства прокси и свойства get
/set
…
// index.js
/**
* @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
// index.ts
function add(input: { a: number, b: number }) {
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
…хотя если вы обнаружите, что пишете код подобным образом, подумайте о том, чтобы перейти на классы.