RObserver是为了解决React Component 不能方便的监控数据变化的痛点。
我们先来看一下 React Component 的生命周期

image
React生命周期图
如果我们需要页面中某个数据(监控对象)发生变化时,产生副作用让另一个数据(作用对象)也发生变化。这个监控对象,可能是父组件传来的值,也可能是redux中的状态,也可能是当前组件的state。
为了实现上述需求。我们需要在 componentWillUpdate 或者 componentDidUpdate 生命周期中,将监控对象改变前后的数据进行对比,再对作用对象做出相应的操作。但是如果监控对象很多,处理起来就会很麻烦。如下代码演示:
componentWillUpdate(nextProps, nextState) { const { count } = this.state; const { targetA, targetB, targetC } = this.props; const { targetA: nTargetA, targetB: nTargetB, targetC: nTargetC } = nextProps; if (targetA !== nTargetA) { this.setState({ count: count + 1 }); } if ( targetB !== nTargetB && !( [targetB, nTargetB].every(t => Array.isArray(t)) && targetB.toString() === nTargetB.toString() ) ) { this.setState({ count: count + 1 }); } if ( targetC !== nTargetC && !( [targetB, nTargetB].every(t => Object.prototype.toString.apply(t) === '[object Object]') && JSON.stringify(targetB) === JSON.stringify(nTargetB) ) ) { this.setState({ count: count + 1 }); } }}
RObserver工具是为了简化该过程,引入工具后只需要改成下面这种就可以了:
constructor(props) { super(props); this.state.rob = new RObserver(); } componentDidMount() { this.state.rob.subscribe(this, { 'props.targetA': this.observerChange, 'props.targetB': { deep: true, handler: this.observerChange, }, 'props.targetC': { deep: true, handler: this.observerChange, }, 'props.targetD': { compareFun: (pre, curr) => { return pre.name === curr.name; }, handler: this.observerChange, }, }); } observerChange = () => { const { count } = this.state; this.setState({ count: count + 1 }); }; componentDidUpdate() { this.state.rob.watch(this); }
RObserver 工具执行逻辑如图:

image
源码如下:
// 监控处理函数type HandlerCb = (pre: any, curr: any) => void;// 监控方法处理方式type Observer = { target: string; // 监控对象 compareFun: (pre: any, curr: any) => boolean; // 对比函数,用户可以自定义是否触发监控回调函数 watcherList: HandlerCb[]; // 监控回调,当上一次记录的值与当前值不同时触发 preValue?: any; // 记录上一次的值 deep?: boolean; // 是否深度监控,如果开启深度监控,将会使用deepCompare来深度对比两个对象,};type HandlerConfig = { handler: HandlerCb; deep: boolean; debounce?: number; // delay有值时开启防抖 compareFun: (pre: any, curr: any) => boolean;};// 所有监控对象type FunMap = { [target: string]: HandlerCb | HandlerConfig;};interface RObserver { // 监控对象映射 obMap: { [target: string]: Observer[]; }; // 注册监控对象 subscribe: (context: Object, funMap: FunMap) => void; // 开启监控 watch: (context: Object) => void; // 注销对象监控 destroy: (target: string, fun?: Function) => void;}class RObserver { constructor() { this.obMap = {}; this.subscribe = function(context: Object, funMap: FunMap) { Object.keys(funMap).forEach(key => { let watcher: Function; let config: any = {}; if (typeof funMap[key] === 'function') { watcher = funMap[key] as HandlerCb; } else if (typeof funMap[key] === 'object') { // 函数防抖 const { handler = () => {}, debounce: delay = 0, compareFun, deep } = funMap[ key ] as HandlerConfig; if (/^[1-9]\d*/.test(delay + '')) watcher = debounce(handler, delay); else watcher = handler; config = { compareFun, deep }; } if (!this.obMap[key]) { const preValue = getValue(key, context); this.obMap[key] = { target: key, preValue: config.deep ? deepCloneObject(preValue) : preValue, watcherList: [watcher], ...config, }; } else if (this.obMap[key].watcherList.indexOf(watcher) === -1) { this.obMap[key].watcherList.push(watcher); } }); }; this.watch = function(context) { Object.values(this.obMap).forEach( ({ target, preValue, watcherList, deep, compareFun }: Observer) => { const value = getValue(target, context); const currValue = deep ? deepCloneObject(value) : value; if (compareFun) { if (!compareFun(preValue, currValue)) { watcherList.forEach(watcher => watcher(preValue, currValue)); } } else if (deep) { if (!deepCompare(preValue, currValue)) { watcherList.forEach(watcher => watcher(preValue, currValue)); } } else { if (preValue !== currValue) { watcherList.forEach(watcher => watcher(preValue, currValue)); } } this.obMap[target].preValue = currValue; } ); }; this.destroy = function(target: string, fun?: Function) { if (!target) throw new Error('必须指定注销目标'); if (!this.obMap[target] || !this.obMap[target].watcherList) throw new Error('注销目标未注册'); if (fun) { this.obMap[target].watcherList = this.obMap[target].watcherList.filter(f => f !== fun); } else { delete this.obMap[target]; } }; }}// 深度对比两个对象export function deepCompare(...args: any[]): boolean { let i, l, leftChain, rightChain; function compare2Objects(x: any, y: any) { let p; // remember that NaN === NaN returns false // and isNaN(undefined) returns true if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') { return true; } // Compare primitives and functions. // Check if both arguments link to the same object. // Especially useful on the step where we compare prototypes if (x === y) { return true; } // Works in case when functions are created in constructor. // Comparing dates is a common scenario. Another built-ins? // We can even handle functions passed across iframes if ( (typeof x === 'function' && typeof y === 'function') || (x instanceof Date && y instanceof Date) || (x instanceof RegExp && y instanceof RegExp) || (x instanceof String && y instanceof String) || (x instanceof Number && y instanceof Number) ) { return x.toString() === y.toString(); } // At last checking prototypes as good as we can if (!(x instanceof Object && y instanceof Object)) { return false; } if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) { return false; } if (x.constructor !== y.constructor) { return false; } if (x.prototype !== y.prototype) { return false; } // Check for infinitive linking loops if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) { return false; } // Quick checking of one object being a subset of another. // todo: cache the structure of arguments[0] for performance for (p in y) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false; } else if (typeof y[p] !== typeof x[p]) { return false; } } for (p in x) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false; } else if (typeof y[p] !== typeof x[p]) { return false; } switch (typeof x[p]) { case 'object': case 'function': leftChain.push(x); rightChain.push(y); if (!compare2Objects(x[p], y[p])) { return false; } leftChain.pop(); rightChain.pop(); break; default: if (x[p] !== y[p]) { return false; } break; } } return true; } // throw "Need two or more arguments to compare"; if (args.length < 1) { return true; } leftChain = []; // this can be cached rightChain = []; if (!compare2Objects(args[0], args[i])) { return false; } return true;}function getValue(key: string, obj: any) { // 匹配 a.b[c.d].e.f 这样的字符 切割成['a','b','c.d','e','f']数组 const keyList = key.split(/\.|\[(.*)\]/g).filter(k => !!k); return keyList.reduce((p, c) => (p || {})[c], obj);}function debounce(func: Function, wait: number = 200): Function { let timeout; function f(...args) { const context = this; if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { func.apply(context, args); }, wait); } return f;}const deepCloneObject = obj => { let clone; if (Object.prototype.toString.apply(obj) === '[object Object]') { if (obj.constructor.name === 'Object') { clone = {}; Object.keys(obj).forEach(key => { clone[key] = deepCloneObject(obj[key]); }); } else { clone = obj; } } else if (Object.prototype.toString.apply(obj) === '[object Array]') { clone = obj.map(e => deepCloneObject(e)); } else { clone = obj; } return clone;};export default RObserver;
另外,RObserver 还提供了 destroy 函数,来解除对监控对象的监控。
创新点
RObserver 除了可以在当前组件内进行监控,还可以让用户通过单例模式实现跨组件的状态监控。
示例如下:
// ob.jsconst rob = new RObserver();export default rob;// a.jsimport rob from 'xx/ob.js';class A extends React.PureComponent { componentDidMount() { rob.subscribe( { targetA: 1 }, { targetA: handler, } ); }}// b.jsimport rob from 'xx/ob.js';class B extends React.PureComponent { componentDidUpdate() { rob.watch({ targetA: 2 }); }}