Proxy实现监听深层对象属性

72 阅读2分钟

背景

最近在做一些好玩的东西的时候遇到了一些麻烦。场景是这样的:我创建了一个ComponentTree这样的对象,数据结构定义大概是这样的。

interface ComponentTree {
 	id: number;
	children: ComponentTree[];
 }

而我需要监听这一颗树,在这个树任意数据改变时都要做出相应的操作,所以想到了使用Proxy。

 

Proxy介绍

Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
通过 Proxy,你可以拦截并定义自定义的操作,以便在目标对象上进行这些操作之前执行一些自定义逻辑。
一般的用法是创建一个代理对象,传入目标对象和一个处理程序对象(handler),处理程序对象中定义了拦截器方法,如 get、set、apply 等。
代理对象可以用来实现属性验证、数据绑定、日志记录、虚拟属性等功能。

 

监听深层对象属性

/**通过这个监听类来监听ComponentTree */
export class WatcherComponentTree {
  constructor(tree: ComponentTree, onChange: () => void) {
    this.onChange = onChange;
    this.proxyTree = this.observe(tree);
  }

  /**监听的树 */
  public proxyTree: ComponentTree;

  /**监听处理函数 */
  public onChange: () => void;

  /**
   * 监听ComponentTree Proxy Reflect
   * @param tree ComponentTree
   * @returns Proxy<ComponentTree>
   */
  public observe(tree: ComponentTree) {
    // 深层遍历对象,给每个属性为对象的添加监听
    return this.createProxy(tree);
  }

  /**
   * 深度遍历创建proxy
   * @param data 需要创建proxy的数据
   * @returns Proxy<any> | any
   */
  private createProxy(data: any) {
    if (_.isObject(data)) {
      if (_.isArray(data)) {
        for (let i = 0; i < data.length; i++) {
          data[i] = this.createProxy(data[i]);
        }
      } else {
        Object.keys(data).forEach((keyItem) => {
          (data as any)[keyItem] = this.createProxy((data as any)[keyItem]);
        });
      }
      return new Proxy(data, {
        get: (target: ComponentTree, key: string, receiver): any => {
          return Reflect.get(target, key, receiver);
        },
        set: (target: ComponentTree, key: string, value) => {
          Reflect.set(target, key, this.createProxy(value));
          // 监听逻辑
          this.onChange();
          return true;
        },
      });
    }

    return data;
  }
}

其实上面的逻辑总结起来很简单,在创建一个Proxy的时候递归地遍历对象所有属性,如果属性也是对象的话则同样也返回一个Proxy的代理对象,相当于递归地监听深层属性。当然不要忘记新加的属性,也就是在set的时候如果是对象也需要创建一个Proxy的代理对象。这样,我们就实现了深层监听一个对象