2
In the qiankun , it is inevitable to encounter communication between applications, such as some customized business requirements based on a certain business format. At this time, it is involved that a certain sub-application needs to know who the current "host" is (identifying which privatization project customized business needs), and to know who the "host" is, the "host" needs to actively inform a certain sub-application , This needs to involve the main and sub-application data communication.

Communication between applications under the micro front end

  • Based on the communication method that comes with the qiankun api in initGlobalState
  • Communication method based on tripartite library: such as @ice/stark-data for communication

Based on qiankun own communication method

https://img2020.cnblogs.com/blog/1080099/202103/1080099-20210305151502519-325252408.png

Based on @ice/stark-data communication method

https://segmentfault.com/img/bVcV9Gt

The communication mode under the micro-front-end architecture mode based on @ice/stark-data can realize the communication mode (full duplex) between the main and sub-applications. The performance is as follows:

  • @/ice/stark-data >Sub: For this scenario, you can use set and get methods of the store object in the 061989d03003c8 library
  • Child -> Main: In view of this scenario, you can use @/ice/stark-data library event object emit , on manner
  • Sub -> Son: In view of this scenario, you can use @/ice/stark-data library store object set , get manner

@ice/stark-data source code analysis

We know that based on the VueJs or ReactJs tripartite state management library vuex or redux , the storage of data is stored in the memory (non-persistent). Similarly, when @ice/stark-data stores data, it is stored based on a namespace combined with a window object, which is also non-persistent. But @ice/startk-data implements a simple publish and subscribe mechanism, sharing data between applications through the global window, the content will be relatively simple under normal circumstances
The vuex and redux are both state management solutions and they are different in usage scenarios.

Note: The currently parsed source code version is 0.1.3 , warehouse address: https://github.com/ice-lab/icestark/tree/master/packages/icestark-data

The source code of the entire @ice/stark-data library is actually relatively simple, consisting of the following parts:

https://segmentfault.com/img/bVcV9D4

  • utils.ts : The tool set contains a total of three functions isObject , isArray , warn , which are used to determine whether a variable is an object, an array type, and a function package for warning message output.
  • cache.ts setCache getCache for access based on the namespace ICESTARK and window global object encapsulation. The namespace is used here, which can also avoid the pollution problem of variables on window
  • store.ts : Mainly realize main application and sub-application, sub-application and sub-application one-way data communication
  • event.ts : Mainly realize the sub-application and the main application
  • index.ts : will store , event for export demand

It can be seen that the core code in the entire library is in the store.ts and event.ts , and then the code in these two files will be analyzed specifically.

store.ts source code analysis

When we need to pass data from the main application to the sub-application, @ice/stark-data is as follows:

  • Main application setting data

    import { store } from '@ice/stark-data'
    store.set('someData', 'darkCode')
  • Sub application receives data

    import { store } from '@ice/stark-data'
    const data: any = store.get('someData')

In store.ts . Between the lines of code 11 to 14 , an interface IO set and get methods are respectively defined in this interface:

interface IO {
  set(key: string | symbol | object, value?: any): void;
  get(key?: StringSymbolUnion): void;
}

The set method receives two parameters, key is a joint type, and value is a variable of any type. This method has no return value.

The get method receives a parameter, and key is a joint type. This method also has no return value?

20 lines 16 to 061989d0300915, an interface Hooks And three methods on , off , has are defined in this interface:

interface Hooks {
  on(key: StringSymbolUnion, callback: (value: any) => void, force?: boolean): void;
  off(key: StringSymbolUnion, callback?: (value: any) => void): void;
  has(key: StringSymbolUnion): boolean;
}

The main function of the interface Hooks is to perform subscription and publication processing for data and "destroy" processing for corresponding "events".

In 22 code, the Store class is defined, which implements the two interfaces IO and Hooks class Store implements IO, Hooks

In class Store respectively defined store with storeEmitter two attributes, and subjected to an initialization operation in the constructor:

  store: object;
  storeEmitter: object;
  constructor() {
    this.store = {};
    this.storeEmitter = {};
  }

Next, two "private" methods are defined, _setValue and _getValue to "write" and "output" the "data" respectively.

  _getValue(key: StringSymbolUnion) {
    return this.store[key];
  }

  _setValue(key: StringSymbolUnion, value: any) {
    this.store[key] = value;
    this._emit(key);
  }

In _setValue , first mount the key attribute to the instance object attribute store object, and set its value to value . While the key by calling _emit the method _getValue extracted corresponding values, and the attribute storeEmitter taken corresponding to "trigger" (in keyEmitter ), and then executes a corresponding correction of its traverse.


Next is the overriding implementation IO interface set and get methods. First look set method ( 67 line to 84 line) implementation:

  set<T>(key: string | symbol | object, value?: T) {
    if (typeof key !== 'string'
      && typeof key !== 'symbol'
      && !isObject(key)) {
      warn('store.set: key should be string / symbol / object');
      return;
    }

    if (isObject(key)) {
      Object.keys(key).forEach(k => {
        const v = key[k];

        this._setValue(k, v);
      });
    } else {
      this._setValue(key as StringSymbolUnion, value);
    }
  }

The internal first judges the key , if it is not one of string , symbol , object return is performed in between. Conversely, first determine if the key variable is a object type, then get the key "object" and traverse it. k corresponding to v in the process of traversal. Call the internal method _setValue instance object to store the data (value); if key is not the object type, call the internal method _setValue instance object to store the data (value).

The get method is to obtain the corresponding stored data (value) key key if it is not one of string , symbol , then return null . Otherwise, call the internal method _getValue to get the value


The next step is to rewrite the three methods in the Hooks First look at the first method on (in 86 row to 106 line):

  on(key: StringSymbolUnion, callback: (value: any) => void, force?: boolean) {
    if (typeof key !== 'string' && typeof key !== 'symbol') {
      warn('store.on: key should be string / symbol');
      return;
    }

    if (callback === undefined || typeof callback !== 'function') {
      warn('store.on: callback is required, should be function');
      return;
    }

    if (!this.storeEmitter[key]) {
      this.storeEmitter[key] = [];
    }

    this.storeEmitter[key].push(callback);

    if (force) {
      callback(this._getValue(key));
    }
  }

on method receives three parameters:

  • key : The parameter key is the combination type of string and symbol
  • callback : The parameter callback is a callback function
  • force : The parameter force is an optional parameter, and the type is a boolean type

It can be seen from the source code implementation that the main function of the on method is the process of storing elements storeEmitter based on the parameters of key and callback If the parameter force is true , use the parameter key to obtain the corresponding value from the method _getValue callback , and execute the callback function callback .

For the second method off (in 108 row to 125 line), achieve the following:

  off(key: StringSymbolUnion, callback?: (value: any) => void) {
    if (typeof key !== 'string' && typeof key !== 'symbol') {
      warn('store.off: key should be string / symbol');
      return;
    }

    if (!isArray(this.storeEmitter[key])) {
      warn(`store.off: ${String(key)} has no callback`);
      return;
    }

    if (callback === undefined) {
      this.storeEmitter[key] = undefined;
      return;
    }

    this.storeEmitter[key] = this.storeEmitter[key].filter(cb => cb !== callback);
  }

off method receives two parameters:

  • key : The parameter key is the combination type of string and symbol
  • callback : The parameter callback is a callback function

As can be seen from the source to achieve, off primary method of action is based on the object instance storeEmitter property of binding key to filter out callback (similar deleting an element from the array.)


Next, create the variable store by calling the function getCache . If the variable store has no value, call the class Store create an instance object and assign the store variable, and mount the variable to window object storeNameSpace as the namespace. Finally, the store variable is derived.

let store = getCache(storeNameSpace);
if (!store) {
  store = new Store();
  setCache(storeNameSpace, store);
}

export default store;

Small summary:

  1. When we call store.set method by key-value pairs ( key , value when setting a value in the form of) the internal first determine key type, if key object type. Then by Object.keys method for acquiring all properties on the object, and (traverse extracted attribute for the property set k ) corresponding to a value ( v ), then calls store inside example _setValue method corresponding to property ( k ) value ( v ) Assign a value to the instance store attribute. Then the internal method _emit method is called, and the corresponding value ( keyEmitter , value ) is obtained from the instance attribute storeEmitter _getValue according to the attribute k Finally, traverse keyEmitter (callback function), and execute its callback function value
  2. In the implementation of the library, the purpose of the use of namespaces is to isolate (directly) cause pollution of the window
  3. When we call the store.get method to obtain the corresponding value through the key|attribute ( key ), the internal will first determine whether key exists, and if it does not exist, then return to the instance object's store attribute; if key exists, but its type is not string / symbol of 061989d0300f9f will return null . On the contrary, call the _getValue method inside the instance to obtain the corresponding value from the instance attribute store through the attribute key
  4. Here, the two attributes store and storeEmitter Store class are involved in the operation of the queue in their definition and value access operations. The types of the two properties are both "object" types, but they can also be defined as array types (from the perspective of the implementation role) or better handled WeakMap

event.ts source code analysis

When processing data from the sub-application to the main application based on @ice/stark-data

  1. Sub application:

    import { event } from '@ice/stark-data'
    event.emit('refreshToken', 'cdacavfsasxxs')
  2. Main application

    import { event } from '@ice/stark-data'
    event.on('refreshToken', (val: any) => { 
      console.log('the value from subApp is:', val)
    })

This kind of realization is realized with the help of publish and subscribe mode, similar to the communication between the child component and the parent component in VueJs Next, understand their internal implementation details from the perspective of source code.

In the code of store.ts 6 defines a constant eventNameSpace in the namespace const eventNameSpace = 'event'; , and the 8 defines a union type StringSymbolUnion (definition of the type), type StringSymbolUnion = string | symbol;

Then in the 10 row to 15 line defines the interface Hooks , and defines in its interior four methods are:

  • emit : emit(key: StringSymbolUnion, value: any): void; The role of this method in the implementation phase is to subscribe to "events", traverse all "events" from the queue, and execute the corresponding callbacks.
  • on : on(key: StringSymbolUnion, callback: (value: any) => void): void; The function of this method in the implementation order is to store the callback function callback in the queue
  • off : off(key: StringSymbolUnion, callback?: (value: any) => void): void; The function of this method in the implementation stage is to find callback from the queue for removal (filtering) operation
  • has : has(key: StringSymbolUnion): boolean; The function of this method in the implementation order is to determine whether the corresponding "value" set exists in the queue key

The 17 line in the event.ts file defines the class Event and implements the Hooks interface class Event implements Hooks . The class Event defines the attribute eventEmitter and initializes it in the constructor.

  eventEmitter: object;

  constructor() {
    this.eventEmitter = {};
  }

The next step is to rewrite the four methods defined in Hooks Let's first look at the implementation on

  on(key: StringSymbolUnion, callback: (value: any) => void) {
    if (typeof key !== 'string' && typeof key !== 'symbol') {
      warn('event.on: key should be string / symbol');
      return;
    }
    if (callback === undefined || typeof callback !== 'function') {
      warn('event.on: callback is required, should be function');
      return;
    }

    if (!this.eventEmitter[key]) {
      this.eventEmitter[key] = [];
    }

    this.eventEmitter[key].push(callback);
  }

As can be seen from source on implementation of the method is also very simple, and store.ts code class Store in on implementation of the method is almost the same. I won't go into details here.

As for the implementation of the other three methods, it is almost the same as the implementation of the corresponding three methods in Store

Finally, by calling the function getCache to create an object variable of type event , then event does not exist (is null, undefined, etc.). Then call the object instance created by the new Event assign the value to the variable event , and call the setCache function to mount the variable event based on the namespace constant eventNameSpace to the window object. Then export the variable event (object type).

Summarize

  • The implementation of full-duplex communication between the main and sub-applications based on @ice/stark-data in the micro-front-end framework qiankun is very simple. The core is based on the publish-subscriber model, distinguished by different namespace variables, and the corresponding attributes ( key ) The value of ( value ) is mounted on the global window object, so that in the same "application", as long as you know the corresponding namespace, you can access its corresponding value;
  • @ice/stark-data source code has some shortcomings, such as Hooks store.ts and event.ts , which did not achieve the effect of reuse (each defined once, unnecessary). In addition, in store.ts and event.ts respectively, the Event Store and 061989d030145f is the same. It is not necessary to implement both of them once. You can write a base class (parent class) and then inherit extends . It is much better to rewrite the method that needs to be rewritten;
  • The code robustness processing in the source code is still good, such as the judgment processing key in the four methods implemented in the Event

前端扫地僧
2.5k 声望1.2k 粉丝