Vue応答式変化

9570 ワード

Vueには、Vueの双方向バインド、すなわち応答式の変化がVue 2において便利な特性がある.Xバージョンでは、Vue応答式の変化はObjectに依存する.definePropertyメソッドは実装されていますが、このメソッドには、arr[0]=11という下付きスケールで値を変更したい場合、Vueはコンポーネントをリスニングして再レンダリングしないこと、arr.length=0という方法で配列をクリアしたい場合、サポートされていないという問題があります.
では、Vueでは、データの双方向バインドをどのように実現しているのでしょうか.簡単にシミュレーションできます.
実装原理は,get/setメソッドをデータにバインドし,対応する属性値を変更すると対応するget/setメソッドがトリガーされ,get/setメソッドでバインドロジックが実装されることである.配列の場合は配列の書き換え方法、オブジェクトの場合はObjectが使用されます.definePropertyメソッド.
まず最も簡単なオブジェクトobjを定義します
let obj = {

  name:"Alice",

  age:18

}

レンダー関数の定義
function render(){
     console.log("  ")
}

オブジェクトobjの各プロパティにget/setメソッドをバインドする
let handler = function(obj,key,value){
    Object.defineProperty(obj,key,{
        get(){
            return value;
        },
        set(newValue){
            if(value!==newValue){
                value = newValue;
                //
                render();
            }
        }
    });
}

handlerメソッドを呼び出してobjのプロパティにget/setをバインド
function observe(obj){
    for(let k in obj){
        handler(obj,k,obj[k])
    }
}
observer(obj);
obj.name = "Fiona"   //     obj    ,   name set  ,    render  ,    

最も簡単なオブジェクトのデータ双方向バインドロジックについて説明しましたが、もし私たちのオブジェクトの属性値がまたオブジェクトであれば?次のように
obj = {
  name:{"name1":"Alice"}
}
このとき,nameにget/setメソッドをバインドしたが,name 1はバインドしていないためobjを用いる.name.name 1="Fina"の場合、name 1のsetメソッドをトリガすることはできません.
上記の方法をさらに改善します.
var handler = function(obj,key,value){
    // value      ,          get/set  
    observe(value);
    Object.defineProperty(obj,key,{
        get(){
            return value;
        },
        set(newValue){
       // , , get/set
       observe(value);
if(value !== newValue){
                value = newValue;
                render();
            }
        }
    })
}

function observe(obj){
    if(typeof(obj)!=="object" || obj === null){
        return obj
    }
    for (let k in obj){
        handler(obj,k,obj[k]);
    }
}

observe(obj);
obj.name.name1 = 'Fiona'; //     name1 set  ,  render()
obj.age = {"age1":19}
obj.age.age1 = 20

ここで,オブジェクト【非配列】の応答的変化は完了するが,オブジェクトに属性を追加しようとしてもトリガーされないという問題がある.
上のobjのようにgenderを追加するにはsetメソッドはトリガーされません.オブジェクトに属性を追加してsetメソッドを同時にトリガーするには$setメソッドを使用します.
vm.$set(obj,newProperty,value)
 
次に配列の応答的変化の実現を見てみましょう
配列は配列のすべてのメソッドを書き換える必要があります.しかし、配列の長さの変化はサポートされておらず、配列の下付きで値を変更する方法もサポートされていません.
配列の一般的な方法は7つあります:pop,push,shift,unshift,splice,sort,reverse
//        
let methods = ['pop','push', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

//          
let ArrayProto = Array.prototype
//              ,             ,                
let proto = Object.create(ArrayProto);

//
methods.forEach(fn=>{
    proto[fn] = function(){
        //           
        ArrayProto[fn].call(this, ...arguments);
    }
}

//       
function render(){
    console.log("  ...")
}

var handler = function(obj, key, value){
    observe(obj);
    Object.defineProperty = function(obj, key, {
        get(){
            return value;
        },
        set(newValue){
            observe(newValue);
            if(value != newValue){
                value = newValue;
                render();
            }
        }
    })
}

function observe(obj){
    if(Array.isArray(obj)){
        //   obj   ,             obj    
        obj.__proto__ = proto;
        return;
    }
    if(typeof(obj) !=="object" || obj===null){
        return obj;
    }
    for(let k in obj){
        handler(obj, k, obj[k]);
    }
}

ここまで、配列の応答式の変化も完了しました
let obj = [1,2,3];
observer(obj);
obj.push(4);//これは、カスタムプロトタイプチェーンのpushメソッドを直接呼び出し、renderを呼び出します.
 
let obj = {
  name:["alice"],
}
observe(obj)
 obj.name.push("fiona")
 
前述のように、definePropertyを使用すると2つの問題があり、配列の長さの変更と配列の下に値を付けるという配列を修正する方法は傍受されないので、Vue 3.xはproxyを使用してデータのバインドを実現し、この2つの問題を解決することができると言っていますが、proxyの互換性は問題です.
次にproxyを用いてデータの応答式を実現する方法を簡単に見てみましょう.
let obj = {
    name:"Fiona",
    loc:{x:100,y:200},
    arr:[1]
}

function render(){
    console.log("  ...")
}

let handler = {
    get(target, key){
        //     loc,value      ,          
        if(typeof(target[key] ==='object' && target[key] !==null)){
            return new Proxy(target[key],handler)
        }
        return Reflect.get(target, key);
    },
    set(target, key, value){
        render();
        Reflect.set(target, key, value);
    }
}

//  obj    ,  get/set  
let proxy = new Proxy(obj, handler);

//       proxy   obj   ,  obj    
proxy.arr.push(2)
proxy.arr.length=0
proxy.loc.x=200