Перейти к содержимому
GitHub

$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!

Если 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

Состояние в 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

…хотя если вы обнаружите, что пишете код подобным образом, подумайте о том, чтобы перейти на классы.