上下文(Context)
上下文(context)让组件能够访问由父组件持有的值,而无需将它们作为 props 向下传递(否则可能需要穿过许多层中间组件,即所谓的"prop-drilling")。
通过使用 createContext 创建一个 [get, set] 函数对,你可以在父组件中设置上下文,并在子组件中获取它:
<!--- file: App.svelte --->
<script>
import Parent from './Parent.svelte';
import Child from './Child.svelte';
</script>
<Parent>
<Child />
</Parent>
<!--- file: Parent.svelte --->
<script>
import { setUserContext } from './context';
let { children } = $props();
setUserContext({ name: 'world' });
</script>
{@render children()}
<!--- file: Child.svelte --->
<script>
import { getUserContext } from './context';
const user = getUserContext();
</script>
<h1>hello {user.name}, inside Child.svelte</h1>
import { createContext } from 'svelte';
interface User {
name: string;
}
export const [getUserContext, setUserContext] = createContext<User>();
[!NOTE]
createContext在 5.40 版本中新增。如果你使用的是更早的 Svelte 版本,则必须使用setContext和getContext替代。
当 Parent.svelte 并不直接知晓 Child.svelte,而是像上面那样将其作为 children 代码片段 的一部分来渲染时,这尤其有用。
setContext 和 getContext
作为 createContext 的替代方案,你可以直接使用 setContext 和 getContext。父组件通过 setContext(key, value) 设置上下文……
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
setContext('my-context', 'hello from Parent.svelte');
</script>
……而子组件通过 getContext 获取它:
<!--- file: Child.svelte --->
<script>
import { getContext } from 'svelte';
const message = getContext('my-context');
</script>
<h1>{message}, inside Child.svelte</h1>
键(上面示例中的 'my-context')以及上下文本身可以是任何 JavaScript 值。
[!NOTE] 更推荐使用
createContext,因为它提供了更好的类型安全性,并且不再需要使用键。
除了 setContext 和 getContext,Svelte 还导出了 hasContext 和 getAllContexts 函数。
在上下文中使用状态
你可以将响应式状态存储到上下文中……
<!--- file: App.svelte --->
<script>
import { setCounter } from './context.ts';
import Child from './Child.svelte';
let counter = $state({
count: 0
});
setCounter(counter);
</script>
<button onclick={() => counter.count += 1}>
increment
</button>
<Child />
<Child />
<Child />
<button onclick={() => counter.count = 0}>
reset
</button>
<!--- file: Child.svelte --->
<script>
import { getCounter } from './context.ts';
const counter = getCounter();
</script>
<p>{counter.count}</p>
import { createContext } from 'svelte';
interface Counter {
count: number;
}
export const [getCounter, setCounter] = createContext<Counter>();
……但要注意,如果你_重新赋值_ counter 而不是更新它,你会"断开链接"——换句话说,不要这样做……
<button onclick={() => counter = { count: 0 } }>
reset
</button>
……你必须这样做:
<button onclick={() => counter.count = 0}>
reset
</button>
如果你处理不当,Svelte 会发出警告。
类似地,要通过上下文传递原始值,请使用将状态传入函数 中描述的函数方式。
组件测试
在编写组件测试 时,创建一个设置上下文的包装组件来检查使用该上下文的组件的行为,往往很有用。自 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: 'Bob' });
return MyComponent(...args);
}
const component = mount(Wrapper, {
target: document.body
});
expect(document.body.innerHTML).toBe('<h1>Hello Bob!</h1>');
unmount(component);
});
取代全局状态
当你拥有被许多不同组件共享的状态时,你可能会想把它放进一个独立的模块中,然后在任何需要的地方导入它:
export const myGlobalState = $state({
user: {
// ...
}
// ...
});
在许多情况下这完全没问题,但存在一种风险:如果你在服务端渲染期间变更了该状态(虽然不推荐,但完全可能!)……
<!--- file: App.svelte --->
<script>
import { myGlobalState } from './state.svelte.js';
let { data } = $props();
if (data.user) {
myGlobalState.user = data.user;
}
</script>
……那么该数据可能会被_下一位_用户访问到。上下文解决了这个问题,因为它不会在请求之间共享。