Dependency injection

Simple and practical state management tool (part 1: Responsive Service)

Reactive -service/ React: a simple and comprehensive front-end status management tool, and a very comprehensive front-end status management solution.

At present, react version has been used in my company’s project, and vUE and other versions will be released later.

What is dependency injection?

  • dependency: refers to theA classorA componentRequired to perform its functionService or object. For the sake of description, we’ll call them allservice.
  • Dependency Injection (DI): is a design pattern in which classes and componentsRequest dependencies from external sourcesRather thanCreate your ownAll of them.

A simple example is the Context Api. When a child component needs a service, it gets it from the Context. The parent component controls which Provider provides it.

In fact, @reaction-service/React dependency injection is implemented through the Context Api, but it is an extension of the Context Api.

Define the service

In general, a service can have any value.

But for more comprehensive dependency injection functionality, let’s start with a class example. It is best to have a Dispose method that performs service instance destruction (more on that later).

In a real-world project, we recommend using the reactive Service described earlier for any Service with state management.

// services/test-a.service.ts
export default class TestAService {
  data = null;

  setData(d) {
    this.data = d;
  }

  getData() {
    return this.data;
  }

  dispose() {
    this.data = null; }}Copy the code

Service injector: Injects services

You can provide a service injector anywhere in your component tree that is responsible for providing a set of services to descendant components.

When a descendant component uses a service provided by the injector, the injector checks if the service has an instance, creates one, and returns an existing instance.

When an injector is unmounted, it takes care of instance destruction of all services it provides, leaving descendant components to worry about.

Providers ={[TestService]}. Instead, we should define a reference variable useRef([TestAService]). Since ServiceInjector received a different provider, it created a new injector, and the service used by this component has changed unexpectedly!

// components/parent.tsx
import { ServiceInjector } from "@reactive-service/react";
import Child from "components/child";
import TestAService from "services/test-a.service";

export default function Parent() {
  const providers = useRef([TestAService]);

  return (
    // Inject TestAService here
    <ServiceInjector providers={providers.current}>
      <Child />
      <Child />
    </ServiceInjector>)}Copy the code

The use of the service

// components/child.tsx
import React, { useCallback } from "react";
import { useService } from "@reactive-service/react";
import TestAService from "services/test-a.service";

export default function Child() {
  // Get an instance of TestAService provided by the parent injector
  const testAService = useService(TestAService);
  console.log(testAService.getData());

  const setData = useCallback((v) = > {
    testAService.setData(v);
  }, [testAService]);

  / /...
  return (<div>{... }</div>);
}
Copy the code

An injector can provide multiple services, and descendant nodes can use different instances of the same service

UseService uses a service from a recent injector that provided that service.

Injectors are also tree-like, and if the service has not been provided by the most recent injector, the parent injector is requested until no parent injector is available.

export default function Parent() {
  // It is possible to provide a service while using it
  const testAService = useService(TestAService);
  const providersA = useRef([TestAService, TestBService]);
  const providersB = useRef([TestAService]);

  return (
    <ServiceInjector providers={providersA.current}>
      <Child />
      <Child />

      <ServiceInjector providers={providersB.current}>
        <Child />
      </ServiceInjector>

    </ServiceInjector>)}Copy the code

Optional services

By default, when useService is called, an exception is thrown if the requested service is not provided in any parent injector.

However, you can set the second parameter {optional: true} to make the service optional.

export default function Test() {
  const testService = useService(TestService, { optional: true });
  if (testService) {
    console.log(testService.getData());
  } else {
    ///
  }

  / /...
  return (<div>{... }</div>);
}
Copy the code

Alternative class: useClass

Providers ={[TestAService]} providers={[{provide: TestAService, useClass: TestAService}]}

The provider may not be the same class as the useClass that actually creates the instance.

function ParentA() {
  return (
    <ServiceInjector 
      providers={[
        {
          provide: Logger.useClass: LoggerA}}] >
      <Child />
    </ServiceInjector>)}function ParentB() {
  return (
    // Inject TestAService here
    <ServiceInjector 
      providers={[
        {
          provide: Logger.useClass: LoggerB}}] >
      <Child />
    </ServiceInjector>)}function Child() {
  const loggerService = useService(Logger);

  / /...
  return (<div>{... }</div>);
}
Copy the code

When the Child component is wrapped in ParentA, loggerService is an instance of LoggerA. When surrounded by ParentB, loggerService is an instance of LoggerB.

In this way, the Child can be reused better by providing a different service if you want it to use different variations of the same service without changing the Child component itself.

Note: useClass should be an extension of the provide class, that is, it should satisfy useClass extends provide.

In the service class, other provided service services can also be used

// components/parent.tsx
import { ServiceInjector } from "@reactive-service/react";
import Child from "components/child";
import RequestService from "services/request.service.ts";
import LoggerService from "services/logger.service.ts";

<ServiceInjector providers={[RequestService, LoggerService]} >
  <Child />
  <Child />
</ServiceInjector>
Copy the code
// services/logger.service.ts
import { InjectionContext } from "@reactive-servic/react";
import RequestService from "services/request.service.ts";

export default class LoggerService {
  reqService: RequestService = null;

  constructor({ useService }: InjectionContext) {
    this.reqService = useService(RequestService);
  }

  log(msg) {
    / /...
    this.reqService.send('/log', { msg }); }}Copy the code

The useService logic is the same as the useService logic in the component.

More and more

  • Reactive Service
  • api