如何将服务注入到 svelte grandchild 组件中?

How to inject a service into svelte grandchild components?

我有几个服务 类(有一些 "get data for these params" 和一些 "calculate stuff for these params" 方法)我想注入我的 Svelte 组件层次结构中的几个组件。目前,我看到了以下选项,none 其中非常有吸引力:

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

一种选择是使用 es6 功能,其中可以更改导出的变量在它们声明的文件中 & 在“服务需要一些”的情况下提供帮助我在入口点 js 中进行的设置。设置来自浏览器环境,因此我无法“融入”配置值”,例如

main.js

import AppUI from './App.html'
import { AppStore }  from './store.js'
import { HttpService } from './http.js'
import { cfg } from './cfg.js'
import { initMain }  from './services.js'

// we're off
initMain( AppUI, AppStore, HttpService, cfg  )

services.js

let app, store, http

function initMain( App, Store, HttpService, cfg ) {

    http = new HttpService(cfg)                         
    store = new Store(cfg)                      
    app = new App({ store, target: document.body })     
  }

}

export { initMain, app, store, http } 

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

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

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

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

在应用程序的顶部创建一个额外的组件来实例化服务并将它们作为道具传递到根 App,然后将它们传递给 setContext。您需要 App 上面的额外组件来确保服务在上下文中设置之前存在。使用 Svelte 的 {#await promise} 语法,您可以确保在传递 prop 之前进行初始化:

InitServices.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

Grandchild.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,它可以工作但会增加一些额外的复杂性(不是 lot,但也不是什么都没有)。这是一个例子: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

Grandchild.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}

Grandchild.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();
});