background

It has been more than 30 years since the front-end development. In the process of continuous expansion, the front-end application field is becoming more and more complex. With the increase of code lines and project requirements, the dependence between internal modules may become more and more complex, and the low reusability between modules leads to the difficulty of application maintenance. Some of these problems can be solved with the help of some of the best programming ideas in computing, such as IoC, which I’ll cover next.

What is the IOC

In fact, if you have learned Java, you will know that there is a very famous framework in Java called Springboot, which is the representative work of using AOP and IOC concepts to the extreme. Then IOC is what it does, we can see the following description.

The IoC, which is called the Inversion of Control, consists of three principles:

  1. High-level modules should not depend on low-level modules, they should all depend on abstractions
  2. Abstraction should not depend on concrete implementation; concrete implementation should depend on abstraction
  3. Program to the interface, not to the implementation

Suppose we have a class Human. To instance Human, we need to instance a class Clothes. To instantiate Clothes, we need to instantiate Cloth, instantiate buttons, and so on.

When the requirements reach a certain degree of complexity, we can not dress for a person to start from cloth and buttons to achieve, it is better to put all the requirements into a factory or warehouse, what we need directly from the factory warehouse.

This is where dependency injection comes in. We implement an IOC container (repository) and then take the clothes from the repository and give them to wear as properties.

This greatly reduces our coding costs.

How do you implement an IOC

In fact, the idea of implementing IOC is very simple, or it is a very light thing, anyone who knows the principles can implement it. First of all, let’s repeat the ioc concept just described. Under normal circumstances, when we need Human and Clothes classes, we can only create them one by one.

export class Human {}

export class clothes {}

function test() {
    const human = new Human();
    const clothes = new Clothes();
}
Copy the code

It’s not hard to see that this is fine when you have a small number of objects that need to be created, but if you have a large system with hundreds or thousands of objects, you need to load different objects in different business scenarios, and you need to control object destruction to avoid GC. So in that sense we’re going to have to do a lot of work to get the front-end objects right, and that brings us to the next thing we need to do how to manage objects.

Step 1: Implement a container

A container is a lofty concept. It is simply something like a Map object that holds existing objects. Below is a small demo of my concrete implementation, which is mainly to store the container class. To keep the container unique, I designed it as a singleton pattern.

export class SimpleContainer {
    private containerMap = new Map<string | symbol, any> ();private static _instance: SimpleContainer;

    public set(id: string | symbol, value: any) :void {
      this.containerMap.set(id, value);
    }
    
    public get<T extends any>(id: string | symbol): T {
      return this.containerMap.get(id) as T;
    }
  
    public has(id: string | symbol): Boolean{
      return this.containerMap.has(id);
    }

    public remove(id: string | symbol): void {
      if (this.containerMap.has(id)) {
          this.containerMap.delete(id); }}public static getInstance(): SimpleContainer {
        if(!this._instance) {
            this._instance = new SimpleContainer();
        }
        return this._instance;
    }

    public get container() :SimpleContainer {
      returnSimpleContainer._instance; }}Copy the code

Step 2: Use decorators

With the introduction of classes in TypeScript and ES6, there are some scenarios where we need additional features to support annotation or modification of classes and their members. Decorators provide a way for us to add annotations to class declarations and members through metaprogramming syntax. Decorators in Javascript are currently in the second phase of the call for proposals, but are already supported as an experimental feature in TypeScript.

Note that decorators are an experimental feature and may change in future releases.

If you need to use a decorator, you should configure experimentalDecorators to enable support for true in tsconfig.json.

First let’s look at the final effect we need to achieve

@Service('human')
export class Human {}

@Service('clothes')
export class clothes {}

export class Test {

    @Inject(a)privatehuman! : Human; }Copy the code

We need to Inject the classes to be instantiated through Service, and then Inject them into the external objects by injecting them. This is what decorators do in IOC.

So how is Service implemented?

export function Service(idOrSingleton? :string | boolean, singleton? :boolean) :Function {
    return (target: ConstructableFunction) = > {
        let id;
        let singleton;
        const container = SimpleContainer.getInstance();
      	// The complexity of the code logic has been reduced
        container.set(id, singleInstance || new target());
    };
};
Copy the code

We all instance initialization is realized in the Service also is such a sentence, the container. The set (id, singleInstance | | new target ()); .

export function Inject(value? :string) :PropertyDecorator {
    return (target: any, propertyKey: string | symbol) = > {  
      const id = value || propertyKey;
      const container = SimpleContainer.getInstance();
      const _dependency = container.get(id) ? container.get(id) : null;
      if (_dependency) {
        target[propertyKey] = _dependency;
      }
      return target;
    };
}
Copy the code

Instantiate and return objects using Inject, which also takes advantage of the ability to assign values to parameters supported by propertyDecorators. When the object corresponding to the decorator is identified, we assign and initialize it through the property decorator.

A little bit of knowledge about decorators is needed here.

1. Decorators change the behavior of classes at compile time, not at run time.

2. Decorators run in order, not according to the class, attribute, method, we need to pay attention to the use of the order is: attribute -> class -> method.

Step 3: Use containers

We are back to the beginning of step 2, when we implement the Inject and Service decorator we can happily initialize.

@Inject(a)privatehuman! : Human;Copy the code

After doing the above, we can use the contents of the object.

Expansion and Outlook

Back to the us to achieve the purpose of the IOC, we hope that through some techniques to manage our stormy objects, and code, so we didn’t do such a container, now, of course, this container is very humble, still have a lot of can expand the space, for example, about the life cycle of control object, how much more friendly use of objects in the container.

reference

Some concepts in the article refer to some big guy’s article, because the time is long already can’t remember clearly, but still want to thank by my reference big guy.

The last

A small AD welcoming the ioc package developed based on the code above is still rudimentary, but I will quickly enhance and iterate on it.

Easy-ts-di:github.com/guanjiangta…

Welcome big guys can offer advice, rebar go away ~~~~