One of my colleagues said to me: There is still not enough demand to build wheels…

preface

The wheel was built from April 22, 2018 to October 12, 2018. After reading an article about the implementation principle of front-end framework routing, I wanted to try to polish a routing, but the result was more and more written, and finally inexplicably turned into an MVVM framework. Incidentally wrote a relatively poor documentation and server render…

Current version: V1.2.0 Project address document NPM

Introduction of InDiv

It’s called InDiv because the component is wrapped in a div. The entire project is written in typescript, which is really nice to say. When thinking about how to write ng React Vue, I refer to the architecture and practice of ng React Vue, and use the best method I can think of to implement it, which is also a practice for myself. (Actually, when writing, I find that the more I write, the more I like NG, maybe I really like Angular too much.) It also implemented a server-side rendering, but it was a bit crude… How I want to pay tribute to the developers of the next three frames at the moment, it is not easy to make wheels

  1. It is divided into nvModules, components, and services.
  2. Templates use string templates, I define some myself for example:nv-class.nv-repeatOnly values from state in the component instance can be used in the template.$.) and instance methods (@), so it looks ugly.
  3. No instruction or pipe for the time being. In a string template, you can use a method with a return value on the component.nv-src="@buildSrc($.src)"), the return value will be rendered to the template, which is not supplemented by pipe.
  4. Own routing, using virtual DOM based asynchronous rendering, but the route lazy loading has not been.
  5. The module is responsible for importing and exporting components, importing other modules, and registering services. Components in the current module can use any services and components from the root module and the current module, as well as components exported from the imported module.
  6. Without a special declaration, a service declared in any module becomes a global singleton, but a component or service can inject only services within the current module or services from the root module; The services declared in the component will follow the component instance, and each component instance will have a separate service instance. (Actually implemented a level 3 injector).
  7. Components implement several life cycles that can be passed in TSimplementsType, whereas in JS you can only write life cycle methods.
  8. throughObject.definePropertyListening to thestate, any direct changesstateAttributes and passsetStateTo change thestateAre synchronous operations that cause a re-rendering of the current component; In the child component, by callingpropsTo change the parent componentstateChild components do not get updated immediatelypropsBecause rendering is asynchronous and not available until after renderingpropos.
  9. Because the use ofObject.definePropertyListening to thestateIf you want to change the array structure, you can only do sosetStateresetstateItem in.
  10. Dependency is implemented in TSconstructorThe parameter type of the token is treated as dependency injection and passed@InjectedDeclarations need to be injected; Only static properties are implemented in JSinjectTokens: string[]Declare the string as the token of the service.
  11. Seal itaxiosAs an HTTP service, and in the Utils class a collection of tools I usually use.

use

component

  1. Through annotationComponentTo provide metadata and declare selector, template, and component providers
  2. Through annotationInjectedTo declare that the following class needs to inject a service
  3. throughimplementsTo implement the lifecycle hook function
  4. provideSetState, GetLocation, SetLocationType, which can be used in componentsths.setState, this.getLocation, this.setLocationAnd other built-in methods to change the state, obtain the route state, set the route state and so on
  5. If you are usingJavaScriptDevelopment, except not useInjectedDeclare that the service needs to be injected through the static property of the classinjectTokens: string[]It’s pretty much the same outside of the injection service
import { Component, SetState, GetLocation, SetLocation, Injected, OnInit, RouteChange, OnDestory } from 'indiv';
import TestService from '.. /.. /service/test';

@Injected
@Component({
    selector: 'app-container-component',
    template: (` 
      
name:{{test.name}} id:{{test.id}}
`
), providers: [{ provide: TestService, useClass: TestService, }, // You can also directly TestService, which acts as a token],})export default class AppContainerComponent implements OnInit, RouteChange, OnDestory { public state: { showSideBar: string; testList: {id: number; name:string; } []; }public setState: SetState; constructor( private testS: TestService, ) { this.subscribeToken = this.testS.subscribe(this.subscribe); } public nvOnInit() { this.state = { showSideBar: 'open', testList: [ { id:0, name: 'dima' }, { id: 1, name: 'xxx'}}; }publicnvRouteChange(lastRoute? :string, newRoute? :string) :void {} public nvOnDestory() { this.subscribeToken.unsubscribe(); } public changeShowSideBar() { if (this.state.showSideBar === 'open') { this.state.showSideBar = 'close'; } else { // this.state.showSideBar = 'open'; You can also use setState this.setState({showSideBar: 'close'}); }}}Copy the code

Services and dependency injection

  1. withangularThe service is similar to the global singleton by default, but can be set in@Injectable({isSingletonMode: false})The specifiedisSingletonModeforfalse, so that the service instance is not created in the IOC container, and each injection re-creates a new service instance through the factory function
  2. Through annotationInjectedServices can also be injected into other services
  3. RXJS is recommended for component communication
  4. Services can be declared in components, modules, but with some differences
  5. It mimics ng’s implementation
import { Subject, Subscription } from 'rxjs';
import { Injectable, Injected } from 'indiv';

@Injected
@Injectable(a)export default class TestService {
  public data: number;
  public subject: Subject<any>;

  constructor(
    private testService2: TestService2
  ) {
    this.data = 1;
    this.subject = new Subject();
  }
  
  public subscribe(fun: (value: any) = > void): Subscription {
    return this.subject.subscribe({
      next: fun,
    });
  }

  public update(value: any) {
    this.subject.next({
      next: value,
    });
  }

  public unsubscribe() {
    this.subject.subscribe(); }}Copy the code

The module

  1. Module by@NvModuleThe decorator takes five arguments declaring that certain components and services belong to this module
  2. A module can export components for use by other modules
  3. The entire application requires a root module, and groups are defined in the root module if routing is not usedbootstrap
import { NvModule } from 'indiv';

import AppContainerComponent from '.. /pages/app.container.component';
import TestService from '.. /service/test.service';
import TestService2 from '.. /service/test2.service'; @NvModule({imports: [], // Imports other module providers: [{provide: TestService, useClass: TestService, }, TestService2, ], components: [ AppContainerComponent, ], exports: [AppContainerComponent,], bootstrap: AppContainerComponent, // Declare the bootstrap component in the root module if routing is not applicable})export default class AppModule { }

import { InDiv } from 'indiv';
const inDiv = new InDiv();
inDiv.bootstrapModule(AppModule);
// inDiv.use(router); Use the routinginDiv.init();
Copy the code

Lifecycle hook

Only the following are implemented, and react is heavily borrowed. In addition, the setter and getter of a class can also be used as a lifecycle

      constructor()
      nvOnInit(): void;
      nvBeforeMount(): void;
      nvAfterMount(): void;
      nvHasRender(): void;
      nvOnDestory(): void;
      nvWatchState(oldState? : State): void;
      nvRouteChange(lastRoute? :string, newRoute? :string): void;
      nvReceiveProps(nextProps: State): void;
Copy the code

Virtual DOM

By converting the DOM structure to a VNode, diff out the difference and applying it to the real DOM, it is similar to the React diff algorithm.

The diff child elements

  1. Diff only sibling children, disallow diff across levels
  2. Elements with the same tagName and key in the old and new VNodes are matched first, and positional differences are calculated
  3. The child elements in the old VNode are put into the removal queue if they do not match
  4. If a child element in the new VNode does not match, its location is found and placed in the insert queue
  5. If the two matched old and new VNodes are not InDiv custom component elements, start diff the attributes, events, etc. of the two matched elements and continue diff the next level of child elements
  6. If the two vNodes are InDiv custom component elements, the diff is passed to the component’s compiler instead of matching the next layer of child elements
  7. Finally, update the queues uniformly within each component, following the procedure of removing, inserting, and replacing properties

to do

  1. Supports custom commands
  2. Route lazy loading
  3. useProxyInstead ofObject.definePropertyOr implement dirty check cancellationstateandprops
  4. @indiv/cli

The last

Other string templates, HTTP, utils routing, etc., are available in the documentation. Please forgive me for my poor writing. If you don’t understand it, you can go to the source of the document, which is completely implemented in Indiv. In fact, the whole project was written on a whim. There were no unit tests, so I guess there were a lot of bugs. As a front-end dish chicken, or in the deep knowledge of their own shortcomings and understand that a good memory is better than a bad pen, more wheels will not go wrong. Finally, thank you for seeing the end