dependency-injection - 如何将服务注入到苗条的孙子组件中?
问题描述
我有几个服务类(有一些“为这些参数获取数据”和一些“为这些参数计算东西”方法)我想注入到我的 Svelte 组件层次结构中的几个组件中。目前,我看到以下选项,它们都不是很有吸引力:
- 将服务作为道具传递。一些中间组件不需要这些服务,只会传递它们。它膨胀了道具的数量。
- 用 store 包装服务。这感觉就像将响应式商店功能误用在它不适合的东西上。来自服务的数据大多是静态的,不是很被动。
- 使用顶级组件中的服务,将结果作为 props 传递给子组件。这会使道具的数量更加膨胀,因为我在根组件和子组件之间有一些“布局”组件。然后,这些布局组件必须传递所有道具。
在 Vue 中,我会编写一个插件来添加所有 Vue 组件中可用的属性。Svelte 的方法是什么?
解决方案
我遇到了同样的问题。当不需要设置时,按照 Rich Harris 的建议并从单独的 JS 文件导出服务很容易,但我有一个问题:延迟加载。与您的问题不同,但相似之处在于服务不会立即可用。
我想通过动态导入延迟加载 Firebase,因为它相当重。这意味着服务在承诺解决之前无法访问数据库import()
,因此导出它们并不那么简单。我找到了几个解决方案:
如果要保持服务静态,请使用上下文
您提到商店似乎有点矫枉过正,因为服务是静态的。您可以为此使用上下文!一个问题:setContext
需要在初始化期间同步调用(即不在onMount
)。如果您的配置同步发生,您应该能够避免额外的层,但这是我所做的:
在应用程序顶部创建一个额外的组件来实例化服务并将它们作为道具传递到根App
,然后将它们传递给setContext
. 您需要上面的额外组件App
来确保服务在上下文中设置之前存在。使用 Svelte 的{#await promise}
语法,您可以确保在传递 prop 之前进行初始化:
初始化服务.svelte
<script>
import config from './config';
import createServices from './services';
import App from './App.svelte';
const services = createServices(config);
</script>
{#await promise then services}
<App {services} />
{/await}
App.svelte
<script>
import Child from './Child.svelte';
export let services;
setContext('services', services);
</script>
<Child /> <-- contains Grandchild.svelte
孙子.svelte
<script>
const services = getContext('services');
const promise = services.getData();
</script>
{#await promise then data}
<div>Hello {data.username}!</div>
{/await}
如果您有组件测试,请使用商店
皱纹#2:我已经用 编写了一些测试@testing-library/svelte
,他们现在抱怨来自的服务,getContext
因为没有父母打电话给setContext
。
一种解决方案是创建一个TestContextWrapper.svelte
,它可以工作但增加了一些额外的复杂性(不是很多,但也不是什么都没有)。这是一个例子:https ://svelte-recipes.netlify.app/testing/#testing-the-context-api
所以...我决定用商店替换上下文,结果效果很好。反应式存储对于测试来说是一个好处,因为您可以使用模拟服务实例化存储并在需要时动态更新它。
我仍然在我的应用程序顶部将服务作为道具传递,以避免空检查,但除此之外,这清理了很多东西。 服务-store.js
import { writable } from 'svelte/store';
export const serviceStore = writable(null);
InitServices.svelte (同上)
App.svelte
<script>
import serviceStore from './services-store';
import Child from './Child.svelte';
export let services;
serviceStore.set(services);
</script>
<Child /> <-- contains Grandchild.svelte
孙子.svelte
<script>
import serviceStore from './services-store';
const promise = $serviceStore.getData();
</script>
{#await promise then data}
<div>Hello {data.username}!</div>
{:catch err}
<p>Error! {err.message}</p>
{/await}
孙子.test.js
import serviceStore from './services-store';
let mockService;
beforeEach(async () => {
mockService = { getData: jest.fn().mockResolvedValue('helloitsjoe') };
serviceStore.set(mockService);
});
...
it('shows username', async () => {
render(Grandchild);
const name = await findByText('helloitsjoe');
expect(name).toBeTruthy();
});
it('shows error', async () => {
// Override mock service
mockService.getData.mockRejectedValue(new Error('oh no!'));
render(Grandchild);
const message = await findByText('oh no!');
expect(message).toBeTruthy();
});
推荐阅读
- linux - 将长参数发送到 python argparser
- php - Angular9 http post CORS问题
- android - 如何在Android中使用retrofit2解析jsonObject发送到字符串?
- ruby-on-rails - 如何从 check_box 中获取哈希值?
- python - 更改张量流图并恢复训练
- java - 如果页面异步更改其内容(无需重新加载页面),如何制作 WebDriverWait?
- assembly - 我正在尝试使用程序集从 1 添加到 10,我必须使用 DI 寄存器
- python - 如何在 python 类中使用概率分布?
- html - 我的引导模式默认打开/损坏
- ros - SLAM/VO中的数据关联和特征匹配有什么区别?