观察者模式下的数据驱动ui显示:修改data数据后,会在观察者响应的回调中传入old,new两个数值进行ui修改,目前包括,数值相同监听(alwaysWatch),数值发生变化监听(watch),支持监听 number boolean string array JsonData等五种格式(不包含对象)
// Observer.ts
/**
* time: 2021/1/25
* func: 观察者对象
*/
export default class Observer{
/** 监控的数据结构 */
private _datas: any = null;
/** 观察者模式锁 */
private _block: boolean = false;
/** 一次性观察者模式锁 */
private _onceBlock: number = 0;
/** 数据改变回调 */
private dataChangeCallback: (key: string, oldValue: any, newValue: any) => void;
/** 当前锁住后,记录需要修改的数据内容 */
private changeValues: Array<any> = [];
/**
* 设置锁
* @param val 值
* @param notify 锁激活后,是同步一下数据内容
*/
public setBlock(val: boolean, notify:boolean = false){
this._block = val;
if(!val && !!notify){
// 同步数据
for(const [name, oldValue, newValue] of this.changeValues){
this.dataChangeCallback(name, oldValue, newValue);
}
}
this.changeValues.length = 0;
}
/**
* 一次性锁,设置锁后,忽略下n次修改,随即自动恢复
* 注意:未消耗完次数前,不会解锁,会影响到setBlock
* @memberof MVVM
* @param times 忽略后n次修改
*/
public setOnceBlock(times: number){
this._onceBlock = times;
}
/**
* 获取锁当前数值
*/
getBlock(){
return this._block;
}
constructor(data:any, func: (key: string, oldValue: any, newValue: any)=>void){
this._datas = data;
this.dataChangeCallback = func;
this.registerData(data);
}
private registerData(datas: any){
for(const data in datas){
const val = datas[data];
this._setObserver(datas, data, val)
}
}
private _setObserver(data: any, name: string, val: any){
let value = val;
const callback = this.dataChangeCallback;
Object.defineProperty(data, name, {
enumerable: true,
configurable: true,
set: (newValue)=>{
//修改值时可直接赋值
const oldValue = Object.assign({}, value);
value = newValue;
// 加锁之后不通知到视图层
if(this._onceBlock > 0){
this._onceBlock--;
return ;
}
if(this._block){
this.changeValues.push([name, oldValue, newValue]);
return
}
callback && callback(name, oldValue, newValue);
},
get: ()=>{
return value;
}
});
}
release(){
this._datas = null;
this._block = false;
this.dataChangeCallback = null;
this.changeValues.length = 0;
}
}
// MVVM
import Observer from './Observer';
/**
* time: 2020/1/25
* func: 观察者模式,数据驱动ui
*/
export default class MVVM{
/** 观察者 */
private observer: Observer = null;
/** Map存储数据结构 */
private keyValueMap: Map<string, Array<Watcher>> = new Map();
/** 监听数据 */
private _data: any = {};
/** 监听数据 */
public get data(){
return this._data;
}
/** 监听数据 */
public set data(val: any){
this._data = val;
if(this.observer){
this.observer.release();
}
this.observer = new Observer(val, this._handler.bind(this));
}
/** 设置锁 上锁/解锁*/
setLock(val: boolean, notify?:boolean){
this.observer && this.observer.setBlock(val, notify);
}
/**
* 一次性锁,设置锁后,忽略下n次修改,随即自动恢复
* 注意:未消耗完次数前,不会解锁,会影响到setBlock
* @memberof MVVM
* @param times 忽略后n次修改
*/
setOnceLock(times:number = 1){
this.observer && this.observer.setOnceBlock(times);
}
/** 获取当前锁的状态 */
getLock(){
return !!this.observer && this.observer.getBlock();
}
private _handler(key: string, oldValue: any, newValue: any){
const watchers = this.keyValueMap.get(key);
if(!watchers){
return ;
}
for(const {targetKey, _thisObj, handler, valueSame} of watchers){
const isSame = this.isSameKeyValue(oldValue, newValue);
if(isSame && !valueSame){
continue;
}
handler && handler.call(_thisObj, oldValue, newValue);
}
}
/**
* 比较两个数值是否相等
* @param oldValue 旧数值
* @param newValue 新数值
*/
isSameKeyValue(oldValue: any, newValue: any): boolean{
let ret = true;
const deal = (dataA: any, dataB: any)=>{
if(dataA === dataB){
return true;
}
if(typeof dataA !== typeof dataB){
ret = false;
return false;
}
if(typeof dataA === 'number' || typeof dataA === 'string' || typeof dataA === 'boolean'){
ret = dataA === dataB;
return ret;
}
else if(Array.isArray(dataA)){
const len = dataA.length;
if(len !== dataB.length){
ret = false;
return false;
}
for(let i = 0; i < len; i++){
if(!deal(dataA[i], dataB[i])){
ret = false;
return false;
}
}
}
else{
if(Object.keys(dataA).length !== Object.keys(dataB).length){
ret = false;
return false;
}
for(const key in dataA){
if(!deal(dataA[key], dataB[key])){
ret = false;
return false;
}
}
}
return ret;
}
deal(oldValue, newValue);
return ret;
}
/**
* 观察属性变化(属性变化后才会通知)
* @param targetKey 目标key
* @param callback 变化时回调
* @param thisObj 当前回调的上下文
*/
public watch(targetKey: string, callback: (oldValue: any, newValue: any) => void, thisObj: any){
this._watch(targetKey, false, callback, thisObj);
}
/**
* 观察属性变化(属性修改就会通知)
* @param targetKey 目标key
* @param callback 变化时回调
* @param thisObj 当前回调的上下文
*/
public alwaysWatch(targetKey: string, callback: (oldValue: any, newValue: any) => void, thisObj: any){
this._watch(targetKey, true, callback, thisObj);
}
/**
* 观察属性变化
* @param targetKey 目标key
* @param callback 变化时回调
* @param thisObj 当前回调的上下文
*/
private _watch(targetKey: string, valueSame: boolean, callback: (oldValue: any, newValue: any) => void, thisObj: any){
if(!Object.prototype.hasOwnProperty.call(this._data, targetKey)){
console.warn(`【MVVM】观察对象中没有该数据:${targetKey}`);
return ;
}
let watchers = this.keyValueMap.get(targetKey);
if(!watchers){
watchers = [];
this.keyValueMap.set(targetKey, watchers);
}
else{
for(const {handler, _thisObj} of watchers){
if(handler === callback && _thisObj === thisObj){
console.warn('重复注册');
return ;
}
}
}
// 注册观察者
const watcher: Watcher = {
targetKey,
handler: callback,
_thisObj: thisObj,
valueSame
}
this.keyValueMap.get(targetKey).push(watcher);
// 首次监听时,执行注册回调,方便初始化相关显示
callback.call(thisObj, null, this.data[targetKey]);
}
/**
* 取消观察
* @param targetKey 目标key
* @param thisObj 当前上下文环境
*/
unwatch(targetKey: string, thisObj: any){
let watchers = this.keyValueMap.get(targetKey);
watchers = watchers.filter(watcher => thisObj !== watcher._thisObj);
if(watchers.length <= 0){
this.keyValueMap.delete(targetKey);
}
else{
this.keyValueMap.set(targetKey, watchers);
}
}
/**
* 取消目标key下的所有观察
* @param targetKey 目标key
*/
unwatchTargetAll(targetKey: string){
this.keyValueMap.delete(targetKey);
}
/**
* 释放所有资源
*/
releaseALl(){
this.keyValueMap.clear();
this.keyValueMap = null;
this.observer.release();
this.observer = null;
this._data = null;
}
}
interface Watcher{
/** 关键key */
targetKey: string,
/** 值修改后回调 */
handler: (oldValue: any, newValue: any) => void,
/** 当前作用域的上下文 */
_thisObj?: any,
/** 值为一致的时候是否通知 */
valueSame: boolean
}
/**
* 用例:let vm = new MVVM();
vm.data = {
name: "1",
age: 2,
eat:["apple", "banana", 9, ["x","a"]],
score:{
math: 60,
shengwu: 90,
yuwen: 99
}
}
vm.watch('name', (oldValue: any, newValue: any)=>{
cc.log(`我改名了,不叫: ${oldValue}, 叫: ${newValue}`);
}, this);
vm.watch('age', (oldValue: any, newValue: any)=>{
cc.log(`我长大了,去年: ${oldValue}岁, 今年: ${newValue}岁`);
}, this);
vm.watch('eat', (oldValue: any, newValue: any)=>{
cc.log(`1我之前喜欢吃: ${oldValue}, 现在只喜欢吃: ${newValue}`);
}, this);
vm.alwaysWatch('eat', (oldValue: any, newValue: any)=>{
cc.log(`2我之前喜欢吃: ${oldValue}, 现在只喜欢吃: ${newValue}`);
}, this);
vm.watch('score', (oldValue: any, newValue: any)=>{
cc.log(`1我去年的分数: ${JSON.stringify(oldValue)}, 我今年的分数: ${JSON.stringify(newValue)}`);
}, this);
vm.alwaysWatch('score', (oldValue: any, newValue: any)=>{
cc.log(`2我去年的分数: ${JSON.stringify(oldValue)}, 我今年的分数: ${JSON.stringify(newValue)}`);
}, this);
this.scheduleOnce(()=>{
vm.data.name = "xuao";
vm.data.age = 3;
vm.setLock(true);
vm.data.eat = ["apple", "banana", 7, ["x","a"]];
vm.data.score = {
math: 60,
shengwu: 90,
yuwen: 99
}
}, 1);
this.scheduleOnce(()=>{
vm.unwatch('eat', this);
vm.unwatchTargetAll("score");
vm.setLock(false, true);
}, 2)
*/

博客探讨了在观察者模式下,如何通过MVVM模式实现数据驱动UI更新。当数据变化时,会触发观察者的回调,传入旧值和新值以更新UI。文章覆盖了对number、boolean、string、array及JsonData五种类型数据的监听,但不包括对象。

2477

被折叠的 条评论
为什么被折叠?



