手写响应式状态管理器

本文分享了一种手写响应式状态管理器的方法,通过创建Observer类来管理和监听状态变化,结合Watcher类实现数据变更的监听与通知。状态管理器的核心是提供一个全局仓库,允许组件存取并实时更新。文章还提供了状态(state)、操作(action)和观察者(watcher)的示例代码,并强调了在实际项目中的应用。
摘要由CSDN通过智能技术生成

手写响应式状态管理器

前端框架的快速发展,催生出一批又一批的轮子。在一些老旧的项目中,打造符合需求的轮子,提高开发效率,成为了应对产品经理和需求的难点和痛点。
这不又快到年底了,为了过好年,在年度考核中脱颖而出,特地和大家共享一个'轮子'(状态管理器)。如果有不足的地方,评论区友好讨论😂。
实现思路:状态管理器的目标是提供一个仓库,对项目中所有的组件开放,各个文件可以在仓库中进行存取操作,如果某一个文件中对仓库中的某一个状态进行了监听,则可以实时获取该状态的最新值。大概的可以用下面的一张图来看:
在这里插入图片描述

实现步骤:
1、

class Observer {
	constructor(obj) {
		this._orData = null; // 被监听的仓库原始状态
		this.state = obj.state; // 被监听的仓库状态
		this.action = obj.action; // 被注册的仓库事件
		this.watcher = obj.watcher; // 观察者
		this.queueActions = new Set(); // 仓库事件队列
		this.obData = Object.create(null); // 代理的仓库状态
		this.reset = null; // 记录观察者
		this.deepClone(obj.state); // 保存原始状态值,这里用到了深拷贝
		this.cacheWatcher(obj.state); // 初始化 观察者
		this.addObserve(); // 初始化仓库事件队列
		this.observable(); // 激活仓库状态代理 
		this.protectValue(['_orData']); // 保护以下划线开头的属性,只读
	}
	observable() {
		let that = this
		this.obData = new Proxy(that.state, {
			set: function (target, propKey, value) {
				if (that.reset) {
					that.reset.colletionObserve({
						"target": target,
						"key": propKey,
						"val": value
					});
				}
				Reflect.set(target, propKey, value);
				return true
			}
		});
	}
	addObserve() {
		this.queueActions.add(Reflect.ownKeys(this.action));
	}
	commit(key, val) { // 可以在提交事件的时候传入参数
		let that = this;
		this.queueActions.forEach(observer => {
			for (let item of observer) {
				if (item == key) {
					that.action[key](that.obData, val);
				}
			}
		});
	}
	cacheWatcher(obj) {
		this.reset = new this.watcher(obj, this._orData); // 注册观察者watcher
	}
	deepClone(target) {
		this._orData = lodash.cloneDeep(target); // 万能的lodash
	}
	protectValue(targetObj) {
		const handler = {
			get(target, key) {
				return target[key];
			},
			set(target, key) {
				console.error(`you can't change ${key} ,cause it's a private property`)
				invariant(key, 'set');
				return true;
			}
		};
		function invariant(key, action) {
			try {
				throw new Error(`Invalid attempt to ${action} private "${key}" property`);
			} catch (e) {
				console.log(e)
			}
		}
		for (let item of targetObj) { // 对带有_开头的对象实现了私有保护,只读
			if (item[0] === '_') {
				this[item] = new Proxy(this[item], handler)
			}
		}
	}
}
let store = new Observer({
	"state": state,
	"action": action,
	"watcher": watcher
});

2、实现watcher

class watcher {
   constructor(obj, orData = null) {
      this.obj = obj;
      this.watchs = null;
      this.temArr = [];
      this.orData = orData; // 源数据已设为私有属性,不可更改
      this.curPage = null; // 缓存当前页面的监听,便于手动清除缓存
      this.changeData = Object.create(null); // 数据变化缓存最新值
   }
   colletionObserve(targetChange) {
      /* 每次提交数据的变动可能并未有新值出现,比如一直提交21,则需要判断是否要触发对应的监听事件,因此这里需要对changeData进行代理操作,避免提交重复的值导致多次触发Observechange*/
      const that = this;
      this.changeData = new Proxy(that.changeData, {
         set: function (target, propKey, value) {
            if (Reflect.has(target, propKey)) { // 检查是否已有相同属性的值
               if (Reflect.get(that.changeData, propKey) == value) { // 如果有相同属性出现,判断是否值相同
                  console.log('nothing to do ') // 如果相同则nothing
                  return false
               } else { // 如果对应的值发生了改变,则通知下级事件
                  Reflect.set(target, propKey, value)
                  return true;
               }
            } else { // 没有相同属性的值则继续
               Reflect.set(target, propKey, value)
               return true;
            }
         } // state 数据发生变动通知观察者
      });
      if (Reflect.set(this.changeData, targetChange.key, targetChange.val)) {
         this.Observechange(targetChange);
      } // 总是缓存最新变动的值
   }
   Observechange(target) {
      let orValue = null,
         that = this,
         newval = null,
         curPage = this.curPage;
      try {
         that.temArr.forEach(item => {
            if (Array.isArray(target)) { // 首次触发的时机,非commit事件
               if (curPage.methods._state_ID == item.Id) { // 触发当前页面监听事件
                  for (let key of target) {
                     if (key == item.originKey) {
                        if (Reflect.has(that.orData, key)) { // 源数据中是否存在监听的Key值
                           if (Reflect.has(that.changeData, key)) { // 是否更新了值
                              newval = Reflect.get(that.changeData, key); // 获取最新的值
                           } else {
                              newval = Reflect.get(that.orData, key) // 返回原始数据
                           }
                           item.fn(Reflect.get(that.orData, key), newval) // 通知相关页面数据变动
                        } else {
                           throw new ReferenceError("Prop name \"" + key + "\" does not exist in state.");
                        }
                     }
                  }
               }
            } else {
               if (target.key == item.originKey) {
                  if (Reflect.has(that.orData, target.key)) {
                     orValue = Reflect.get(that.orData, target.key); // 更新旧值
                  }
                  item.fn(orValue, target.val) // 通知相关页面数据变动
               }
            }
         });
      } catch (e) {
         console.log(e)
      }
   }
   typeCheck(obj) {
      return typeof obj !== "object" ? typeof obj : Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
   }
   watch(params) {
      let res = false;
      if (this.curPage) {
         res = this.differ(Reflect.ownKeys(params.methods), Reflect.ownKeys(this.curPage.methods))
      }
      if (!res) { // 避免重复添加
         const timestamp = new Date().getTime(),
            keys = Reflect.ownKeys(params.methods);
         let fn = null,
            temObj = {};
         Reflect.set(params.methods, '_state_ID', timestamp); // 标识页面
         this.curPage = params // 缓存
         for (let key of keys) {
            if (typeof (key) != 'symbol' && key.substring(0, 7) != '_state_') {
               if (Reflect.has(this.orData, key)) {
                  fn = params.methods[key]
                  if (this.typeCheck(fn) == 'function') {
                     Reflect.set(temObj, 'originKey', key);
                     Reflect.set(temObj, 'Id', timestamp);
                     Reflect.set(temObj, 'fn', fn);
                     this.temArr.push(temObj);
                     temObj = {};
                  } else {
                     console.error(`You can't watch ${key},because it is not a function`)
                  }
               } else {
                  console.error(`You can't watch ${key},because it is not be created in state`)
               }
            } else {
               try {
                  throw new ReferenceError("You cannot set a key that begins with a '_state_',because it's a protect property");
               } catch (error) {
                  console.log(error)
               }
            }
         }
         this.Observechange(keys); // 开始监听
      }

   }
   differ(pageWatch, tem) { // 找出两个数组的交集
      let temLen = tem.length - 1;
      if (Reflect.has(tem, '_state_ID')) {
         temLen = temLen - 1
      }
      let pageLen = pageWatch.length;
      const set_intersection = (set1, set2) => {
         if (set1.size > set2.size) {
            return set_intersection(set2, set1);
         }
         const intersection = new Set();
         for (const num of set1) {
            if (set2.has(num)) {
               intersection.add(num);
            }
         }
         return [...intersection];
      }
      let intersection = function (nums1, nums2) {
         const set1 = new Set(nums1);
         const set2 = new Set(nums2);
         return set_intersection(set1, set2);
      };
      const intersectionArr = intersection(pageWatch, tem)
      if (intersectionArr.length != temLen || intersectionArr.length != pageLen) {
         return false // 两次数组不一致 返回false
      }
      return true // 两次数组一致 返回true
   }
   clearWatchMemory() {
      const pageWatch = this.curPage;
      if (!pageWatch) { // 如果当前页面没有pageWatch 说明还未有监听方法在执行或者入参错误
         try {
            throw new ReferenceError("You cannot run clearWatchMemory method,because the Memory is empty");
         } catch (error) {
            console.log(error)
         }
         return
      } else {
         let pageData = pageWatch.methods;
         let pageId = Reflect.get(pageData, '_state_ID')
         this.temArr = this.temArr.filter(res => res.Id != pageId) // 清除缓存
         this.curPage = null
      }
   }
}

3、 新建state.js

const state = {
  message:'Hello World!'
}
exports.state = state

4、action同上

const action = {
  messageChange: function (state,val) {
        state.message = val
    }
}
exports.action = action

实际上,上面的响应式管理器,已经运用在实际的项目中了。这其中做了不少关于检测的工作。但百密一疏,总会有疏漏的地方,如果有发现的地方,评论区给出,有奖奥😀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铁锅炖大鹅(e)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值