Skip to main content

上下文 API 提供了一种组件之间相互 'talk' 的机制,无需将数据和函数作为 props 传递,也无需分派大量事件。 这是一项高级功能,但却很有用。

The context API provides a mechanism for components to 'talk' to each other without passing around data and functions as props, or dispatching lots of events. It's an advanced feature, but a useful one.

以使用 Mapbox GL 映射的示例应用为例。 我们希望使用 <MapMarker> 组件显示标记,但我们不希望必须传递对底层 Mapbox 实例的引用作为每个组件上的 prop。

Take this example app using a Mapbox GL map. We'd like to display the markers, using the <MapMarker> component, but we don't want to have to pass around a reference to the underlying Mapbox instance as a prop on each component.

上下文 API 分为两部分 — setContextgetContext。 如果组件调用 setContext(key, context),则任何子组件都可以使用 const context = getContext(key) 检索上下文。

There are two halves to the context API — setContext and getContext. If a component calls setContext(key, context), then any child component can retrieve the context with const context = getContext(key).

让我们先设置上下文。 在 Map.svelte 中,从 svelte 导入 setContext,从 mapbox.js 导入 key,并调用 setContext

Let's set the context first. In Map.svelte, import setContext from svelte and key from mapbox.js and call setContext:

ts
import { onDestroy, setContext } from 'svelte';
import { mapbox, key } from './mapbox.js';
setContext(key, {
getMap: () => map
});

上下文对象可以是你喜欢的任何对象。 与 生命周期函数 一样,setContextgetContext 必须在组件初始化期间调用。 之后调用它 - 例如在 onMount 内 - 将引发错误。 在此示例中,由于 map 在组件安装后才创建,因此我们的上下文对象包含 getMap 函数而不是 map 本身。

The context object can be anything you like. Like lifecycle functions, setContext and getContext must be called during component initialisation. Calling it afterwards - for example inside onMount - will throw an error. In this example, since map isn't created until the component has mounted, our context object contains a getMap function rather than map itself.

在等式的另一边,在 MapMarker.svelte 中,我们现在可以获得对 Mapbox 实例的引用:

On the other side of the equation, in MapMarker.svelte, we can now get a reference to the Mapbox instance:

ts
import { getContext } from 'svelte';
import { mapbox, key } from './mapbox.js';
const { getMap } = getContext(key);
const map = getMap();

标记现在可以将自己添加到映射中。

The markers can now add themselves to the map.

<MapMarker> 的更完整版本还将处理删除和属性更改,但我们在这里仅演示上下文。

上下文键(Context keys)

mapbox.js 中你会看到这一行:

In mapbox.js you'll see this line:

ts
const key = Symbol();

从技术上讲,我们可以使用任何值作为键 — 例如,我们可以做 setContext('mapbox', ...)。 使用字符串的缺点是不同的组件库可能会意外地使用同一个字符串; 另一方面,使用 symbols 意味着可以保证键在任何情况下都不会发生冲突,即使你在多个组件层上运行多个不同的上下文,因为符号本质上是唯一标识符。

Technically, we can use any value as a key — we could do setContext('mapbox', ...) for example. The downside of using a string is that different component libraries might accidentally use the same one; using symbols, on the other hand, means that the keys are guaranteed not to conflict in any circumstance, even when you have multiple different contexts operating across many component layers, since a symbol is essentially a unique identifier.

上下文与存储(Contexts vs. stores)

上下文和存储看起来很相似。 它们的不同之处在于,存储可用于应用的任何部分,而上下文仅可用于组件及其后代。 如果你想要使用某个组件的多个实例,而又不想其中一个实例的状态干扰其他实例的状态,那么这会很有帮助。

Contexts and stores seem similar. They differ in that stores are available to any part of an app, while a context is only available to a component and its descendants. This can be helpful if you want to use several instances of a component without the state of one interfering with the state of the others.

事实上,你可以将两者一起使用。 由于上下文不是反应性的,因此随时间变化的值应该表示为存储:

In fact, you might use the two together. Since context is not reactive, values that change over time should be represented as stores:

ts
const { these, are, stores } = getContext(...);