MVVM模式,数据驱动ui

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值