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

Контекст

Контекст позволяет компонентам получать доступ к значениям, принадлежащим родительским компонентам, без необходимости передавать их через пропсы (избегая передачи через множество промежуточных компонентов, что также называют «пробросом пропсов» («prop-drilling»)). Родительский компонент устанавливает контекст с помощью setContext(key, value)

Parent.svelte
<script>
import { setContext } from 'svelte';
setContext('my-context', 'привет из Parent.svelte');
</script>

…а дочерний компонент получает его с помощью getContext.

Child.svelte
<script>
import { getContext } from 'svelte';
const message = getContext('my-context');
</script>
<h1>{message}, внутри Child.svelte</h1>

Это особенно полезно, когда Parent.svelte не знает напрямую о Child.svelte, а вместо этого отображает его как часть сниппета children (демонстрация).

<Parent>
<Child />
</Parent>

Ключ ('my-context' в примере выше) и сам контекст могут быть любыми значениями JavaScript.

Помимо setContext и getContext, Svelte предоставляет функции hasContext и getAllContexts.

Вы можете хранить реактивное состояние в контексте (демонстрация)…

<script>
import { setContext } from 'svelte';
import Child from './Child.svelte';
let counter = $state({
count: 0
});
setContext('counter', counter);
</script>
<button onclick={() => counter.count += 1}>
увеличить
</button>
<Child />
<Child />
<Child />

…однако обратите внимание, что если вы переприсваиваете counter вместо его обновления, вы «разорвете связь» — другими словами, вместо этого…

<button onclick={() => counter = { count: 0 }}>
сбросить
</button>

…вы должны делать так:

<button onclick={() => counter.count = 0}>
сбросить
</button>

Svelte предупредит вас, если вы сделаете что-то неправильно.

В качестве альтернативы использованию setContext и getContext напрямую, вы можете использовать их через createContext. Это обеспечивает типобезопасность и делает ненужным использование ключа:

context.ts
import { createContext } from 'svelte';
export const [getUserContext, setUserContext] = createContext<User>();

При написании компонентных тестов бывает полезно создать обёрточный компонент, который устанавливает контекст, чтобы проверить поведение компонента, который этот контекст использует. Начиная с версии 5.49, можно делать примерно следующее:

import { mount, unmount } from 'svelte';
import { expect, test } from 'vitest';
import { setUserContext } from './context';
import MyComponent from './MyComponent.svelte';
test('MyComponent', () => {
function Wrapper(...args) {
setUserContext({ name: 'Вован' });
return MyComponent(...args);
}
const component = mount(Wrapper, {
target: document.body
});
expect(document.body.innerHTML).toBe('<h1>Привет, Вован!</h1>');
unmount(component);
});

Этот подход также работает с hydrate и render.

Когда у вас есть состояние, которое используется многими компонентами, может возникнуть соблазн поместить его в отдельный модуль и просто импортировать там, где это необходимо:

state.svelte.js
export const myGlobalState = $state({
user: {
// ...
}
// ...
});

Во многих случаях это вполне допустимо, но есть риск: если вы изменяете состояние во время рендеринга на стороне сервера (что не рекомендуется, но всё же возможно)…

App.svelte
<script>
import { myGlobalState } from 'svelte';
let { data } = $props();
if (data.user) {
myGlobalState.user = data.user;
}
</script>

…то данные могут стать доступными для следующего пользователя. Контекст решает эту проблему, так как он не является общим между запросами.