如何将服务注入到苗条的孙子组件中?

chi*_*org 10 dependency-injection svelte

我有几个服务类(其中一些“获取这些参数的数据”和一些“计算这些参数的内容”方法)我想注入到我的 Svelte 组件层次结构中的几个组件中。目前,我看到了以下选项,但没有一个非常有吸引力:

  • 将服务作为道具传递。一些中间组件不需要服务,只会传递它们。而且它使道具的数量膨胀。
  • 用商店包装服务。这感觉就像滥用反应式存储功能来做一些不该做的事情。从服务中出来的数据大多是静态的,反应性不强。
  • 使用顶级组件中的服务,将结果作为 props 传递给子组件。这会使道具的数量更加膨胀,因为我在根组件和子组件之间有一些“布局”组件。然后,这些布局组件必须传递所有道具。

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

hel*_*joe 11

我遇到了同样的问题。当不需要设置时,很容易执行 Rich Harris 的建议并从单独的 JS 文件导出服务,但我遇到了一个问题:延迟加载。与您的问题不同,但相似之处在于服务无法立即可用。

我想通过动态导入来延迟加载 Firebase,因为它相当重。这意味着在承诺解决之前,服务无法访问数据库import(),因此导出它们并不那么简单。我找到了几个解决方案:

如果您想保持服务静态,请使用上下文

您提到商店似乎有点矫枉过正,因为服务是静态的。您可以为此使用上下文!有一个问题:setContext需要在初始化期间同步调用(即不在 中onMount)。如果您的配置同步发生,您应该能够避免额外的层,但这就是我所做的:

在应用程序顶部创建一个额外的组件来实例化服务并将它们作为 props 传递到 root 中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}
Run Code Online (Sandbox Code Playgroud)

应用程序.svelte

<script>
  import Child from './Child.svelte';
  export let services;

  setContext('services', services);
</script>

<Child />    <-- contains Grandchild.svelte
Run Code Online (Sandbox Code Playgroud)

孙子.苗条

<script>
  const services = getContext('services');
  const promise = services.getData();
</script>

{#await promise then data}
  <div>Hello {data.username}!</div>
{/await}
Run Code Online (Sandbox Code Playgroud)

如果您有组件测试,请使用商店

皱纹#2:我用 编写了一些测试@testing-library/svelte,他们现在抱怨来自 的服务getContext,因为没有家长打电话setContext

一种解决方案是创建一个TestContextWrapper.svelte,它可以工作,但会增加一些额外的复杂性(不是很多但也不是没有)。这是一个例子:https://svelte-recipes.netlify.app/testing/#testing-the-context-api

所以...我决定用商店替换上下文,结果效果很好。反应式存储对于测试来说是一个好处,因为您可以使用模拟服务实例化存储,并在需要时动态更新它。

我仍然将服务作为应用程序顶部的支柱传递,以避免空检查,但除此之外,这还清理了很多东西。 服务商店.js

import { writable } from 'svelte/store';

export const serviceStore = writable(null);
Run Code Online (Sandbox Code Playgroud)

InitServices.svelte (同上)

应用程序.svelte

<script>
  import serviceStore from './services-store';
  import Child from './Child.svelte';

  export let services;

  serviceStore.set(services);
</script>

<Child />    <-- contains Grandchild.svelte
Run Code Online (Sandbox Code Playgroud)

孙子.苗条

<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}
Run Code Online (Sandbox Code Playgroud)

孙子.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();
});
Run Code Online (Sandbox Code Playgroud)

  • 感谢您对如何实现这一目标的详细解释。这一切确实让人感觉需要做大量的工作(还有额外的注意事项)才能基本上实现 IoC(控制反转),这是一个经过验证的多功能工具,我认为对于更严肃的框架来说应该是开箱即用的。我觉得这正是编译器应该能够做到的,所以我将找出为什么没有更好地考虑这一点。从 Angular 开始使用 Svelte 已经有 2 天了(我真的很喜欢 Svelte),我已经觉得从长远来看这会让我烦恼并咬我一口。 (2认同)