MVVM模式,数据驱动ui

观察者模式下的数据驱动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)
 */

你可能感兴趣的:(TS,cocosCreator,typescript,设计模式)