首页 > 解决方案 > 如何将服务注入到苗条的孙子组件中?

问题描述

我有几个服务类(有一些“为这些参数获取数据”和一些“为这些参数计算东西”方法)我想注入到我的 Svelte 组件层次结构中的几个组件中。目前,我看到以下选项,它们都不是很有吸引力:

在 Vue 中,我会编写一个插件来添加所有 Vue 组件中可用的属性。Svelte 的方法是什么?

标签: dependency-injectionsvelte

解决方案


我遇到了同样的问题。当不需要设置时,按照 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();
});

推荐阅读