观察者模式下的数据驱动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 dataChangeCallback: (key: string, oldValue: any, newValue: any) => void;
/** 当前锁住后,记录需要修改的数据内容 */
private changeValues: Array = [];
/**
* 设置锁
* @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;
}
/**
* 获取锁当前数值
*/
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){
let val = datas[data];
this._setObserver(datas, data, val)
}
}
private _setObserver(data: any, name: string, val: any){
let value = val;
let callback = this.dataChangeCallback;
Object.defineProperty(data, name, {
enumerable: true,
configurable: true,
set: (newValue)=>{
if(this._block){
this.changeValues.push([name, value, newValue])
return
}
let oldValue = value;
value = newValue;
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 _data: any = {};
private observer: Observer = null;
private keyValueMap: Map> = new Map();
get data(){
return this._data;
}
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);
}
getLock(){
return !!this.observer && this.observer.getBlock();
}
private _handler(key: string, oldValue: any, newValue: any){
let watchers = this.keyValueMap.get(key);
if(!watchers){
return ;
}
for(const {targetKey, _thisObj, handler, valueSame} of watchers){
let isSame = this.isSameKeyValue(oldValue, newValue);
if(isSame && !valueSame){
continue;
}
handler && handler.call(_thisObj, oldValue, newValue);
}
}
isSameKeyValue(oldValue: any, newValue: any): boolean{
let ret = true;
let deal = (dataA: any, dataB: any)=>{
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)){
let 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(!this._data.hasOwnProperty(targetKey)){
cc.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){
cc.warn("重复注册");
return ;
}
}
}
// 注册观察者
let watcher: Watcher = {
targetKey,
handler: callback,
_thisObj: thisObj,
valueSame
}
this.keyValueMap.get(targetKey).push(watcher);
}
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);
}
}
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)
*/