编程知识 cdmana.com

Abandon react Redux: implementation of non intrusive state sharing

Preface

   as everyone knows ,Redux It solves the problem of data exchange between components , A series of plug-ins are provided to monitor and debug the application .

   Just Redux There is no invasion in itself , It is react-redux Widely used connect Leading to an invasion of components

   Even though Hooks API Of useSelector and useDispatch Has been transformed n The order of magnitude of invasiveness is 1 —— <Provider/> Component pair React The invasiveness of root applications , It's still invasive

   On the other hand, it is also used reducer + action + payload Separate the internal independent functions of the original components It's very unnecessary .

   So I decided to design and implement a 『 Publish subscribe module 』, Meet the data exchange requirements of general abstraction .

  ( Why not call 『 State sharing module 』? Because I don't use reducer + action + payload I dare not call others the same title )

   Originally intended to use redux As an underlying implementation , Considering the business needs, we just use JS Built in data structure , It's easy to compare yourself with Redux The team gap

 

Design and implementation

   The implementation here is based on the agreement of the front-end module system I developed earlier , because github The network is often unstable , Moved the project to... Two days ago getee:

  https://gitee.com/jhxlhl1023/m-00-codebase-frontend

  

import { consts } from "@/module-00-codebase/pkg-00-const";
import { Bi } from "@/module-00-codebase/pkg-01-container";
import { BaseElmt } from "@/module-00-codebase/pkg-03-abstract";

export class Broker {
  public read(propertyPath: string): any;
  public read(propertyPath: string, subscribeFuncKey: any, subscribeFunc: () => void): any;
  public read(propertyPath: string, subscribeFuncKey?: any, subscribeFuncCall?: () => void): any {
    let properties: string[];
    if (currentBindingElmt === null && !(subscribeFuncKey && subscribeFuncCall)) {
      // skip
    } else if ((properties = Bi.utils.splitProperties(propertyPath)).length === 0) {
      throw new Error("Cannot read value from Broker with an empty key.");
    } else {
      let parent = map;
      let node: SubscribeNode;
      for (let i = 0, length = properties.length; i < length; i++) {
        const tempNode = parent.get(properties[i]);
        if (!tempNode) {
          node = { key: properties[i], elmtSet: new Set(), functionMap: new Map(), leafs: new Map() };
          parent.set(properties[i], node);
        } else {
          node = tempNode;
        }
        parent = node.leafs;
        if (i === length - 1) {
          if (subscribeFuncKey && subscribeFuncCall) {
            node.functionMap.set(subscribeFuncKey, subscribeFuncCall);
          } else if (currentBindingElmt) {
            node.elmtSet.add(currentBindingElmt);
          }
        }
      }
      return Bi.utils.readValue(cache, propertyPath);
    }
  }
  public write(propertyPath: string, value: any): any {
    Bi.utils.writeValue(cache, propertyPath, value);
    const keys = Bi.utils.splitProperties(propertyPath);
    const elmtSet = new Set<BaseElmt<any>>();
    const funcSet = new Set<() => void>();
    const parent = map;
    let nodeOfKey: SubscribeNode | undefined = undefined;
    debugger;
    for (let i = 0, length = keys.length; i < length; i++) {
      const node = parent.get(propertyPath);
      if (!node) break;
      else addToSet(node, elmtSet, funcSet);
      if (i === keys.length - 1) nodeOfKey = node;
    }
    if (!!nodeOfKey) addToSetCascade(nodeOfKey, elmtSet, funcSet);
    funcSet.forEach(func => func.call(null));
    elmtSet.forEach(elmt => elmt.elmtRefresh());
  }
}
export const initializing = async () => {
  const oldRender = BaseElmt.prototype.render;
  const newRender = function (this: BaseElmt<any>) {
    const oriElmt = currentBindingElmt;
    currentBindingElmt = this;
    try {
      return oldRender.call(this);
    } finally {
      currentBindingElmt = oriElmt;
    }
  };
  BaseElmt.prototype.render = newRender;
};
export const order = () => consts.firstOrder;
const addToSetCascade = (node: SubscribeNode, elmtSet: Set<BaseElmt<any, any>>, funcSet: Set<() => void>) => {
  addToSet(node, elmtSet, funcSet);
  node.leafs.forEach(node => addToSetCascade(node, elmtSet, funcSet));
};
const addToSet = (node: SubscribeNode, elmtSet: Set<BaseElmt<any, any>>, funcSet: Set<() => void>) => {
  node.elmtSet.forEach(elmt => (elmt.isDestroyed ? node.elmtSet.delete(elmt) : elmtSet.add(elmt)));
  node.functionMap.forEach(func => funcSet.add(func));
};
const cache = {} as any;
const map = new Map<string, SubscribeNode>();
let currentBindingElmt: BaseElmt<any> | null = null;

type SubscribeNode = { key: string; elmtSet: Set<BaseElmt<any>>; functionMap: Map<any, () => void>; leafs: Map<string, SubscribeNode> };

 

 

Easy to use

//  Monitoring when the value and setting data change anywhere 
const name = Bi.broker.read("student.teachers[1].name"," Prevent duplicate subscription  student.teachers[1].name  Of  key", ()=>console.log(" Teachers' 1 My name has changed "));
//  A subscription in a component can be abbreviated as 
const name = Bi.broker.read("student.teachers[1].name");
//  Short for subscription in a component is equivalent to 
const name = Bi.broker.read("student.teachers[1].name",this,()=>this.elmtRefresh());

//  Update values anywhere , Will trigger monitoring such as component refresh 
Bi.broker.write("student.teachers[1].name"," Teachers' 1 My new name ");
// Don't consider teacher There are other properties , It is also equivalent to writing like this , The difference is in the scope of the trigger monitor student.teachers[1].name and student.teachers[1], It's easy to understand , The latter is more extensive
Bi.broker.write("student.teachers[1]",{ name: " Teachers' 1 My new name " });

 

notes :

Bi yes IOC The IoC Container , Modules that meet the development specifications are dynamically initialized and injected into Bi in , Use 『Bi. The first letter of a class is lowercase 』 Call in the form of .

About IOC I won't go into details about this platitude , The implementation of the specific module factory can be seen in the above document Gitee Open source project links

 

summary :

Finally, it's non intrusive 、 More general state sharing

Abandoned react-redux Intrusive 、 and redux Less applicable, using more complex and unnecessary development patterns

There is still room for performance optimization ( Such as delayed merge trigger 、 Call update but the value does not change, etc )

There are some automatic cleaning methods for memory occupation , In the controllable range, the subscription object is not in Map Unlimited expansion in cache , But there is still room for optimization , Welcome to give your valuable comments .

 

 

版权声明
本文为[Mr. benki]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/04/20210422043737551y.html

Scroll to Top