ES 6 ProxyがVueの変化検出を実現


Vue変化検出ObjectはDefineProperty、配列使用方法でブロックして実現します。最近、Vue 3.0はES 6 Proxyという形でVueの変化検出を再実現します。公式がまだ新しい方法を提供していない前に、まずProxyによる変化検出を実現します。
モジュール区分
以前のVue変化検出コードを参照して、Vue変化検出の機能を以下のいくつかの部分に分けます。
  • Observer
  • Dep
  • Watch
  • Utils
  • まず、私達が確定したい問題は、Depをコレクションに依存してどこに存在しますか?Vue 2.xではObjectの依存コレクションをdefineRactiveに置き、Arayの収集をObserverに預けます。ES 6 Proxyでは、handlerにdepを訪問させることを考慮して、Observerに依存します。
    Observer
    observer.js機能コードは以下の通りです。
    
    import Dep from './dep';
    import { isObject } from './utils';
    export default class Observer {
      constructor (value) {
        //        
        this.obeserve(value);
        //          
        this.value = this.proxyTarget(value);
      }
      proxyTarget (targetBefore, keyBefore) {
        const dep = new Dep();
        targetBefore.__dep__ = dep;
        let self = this;
        const filtersAtrr = val => ['__dep__', '__parent__'].indexOf(val) > -1;
        return new Proxy(targetBefore, {
          get: function(target, key, receiver){
            if (filtersAtrr(key)) return Reflect.get(target, key, receiver);
            if (!Array.isArray(target)) {
              dep.depend(key);
            }
            // sort/reverse         , get   
            if (Array.isArray(target)) {
              if ((key === 'sort' || key === 'reverse') && target.__parent__) {
                target.__parent__.__dep__.notify(keyBefore);
              }
            } 
            return Reflect.get(target, key, receiver);
          },
          set: function(target, key, value, receiver){
            if (filtersAtrr(key)) return Reflect.set(target, key, value, receiver);
            //     ,  proxy
            const { newValue, isChanged } = self.addProxyTarget(value, target, key, self);
            //   key    
            Reflect.set(target, key, newValue, receiver);
            // notify
            self.depNotify(target, key, keyBefore, dep, isChanged);
            return true;
          },
        });
      }
      addProxyTarget(value, target, key, self) {
        let newValue = value;
        let isChanged = false;
        if (isObject(value) && !value.__parent__) {
          self.obeserve(newValue);
          newValue = self.proxyTarget(newValue, key);
          newValue.__parent__ = target;
          isChanged = true;
        }
        return {
          newValue,
          isChanged,
        }
      }
      depNotify(target, key, keyBefore, dep, isChanged) {
        if (isChanged && target.__parent__) {
          target.__parent__.__dep__.notify(keyBefore);
          return;
        }
        if (Array.isArray(target)) {
          if (key === 'length' && target.__parent__) {
            target.__parent__.__dep__.notify(keyBefore);
          }
        } else {
          dep.notify(key);
        }
      }
      obeserve(target) {
        //        ,    、  
        if (!isObject(target)) return;
        for (let key in target) {
          if (isObject(target[key]) && target[key] !== null) {
            this.obeserve(target[key]);
            target[key] = this.proxyTarget(target[key], key);
            //   __parent__,       
            target[key].__parent__ = target;
          }
        }
      }
    }
    Observerでは、対象に対して dep.depend(key) dep.notify(key)を実行すればいいです。keyを追加するのは正確に収集を触発するためで、どうしてこのようにしますか?
    Arayは、依存の収集とトリガをどのように実現しますか?依存収集はObjectと類似しており、  dep.depend(key)は、配列の収集を完了する。トリガについては、配列長、二変化なしの配列長の2つの態様に分けることができる。配列の長さを変更するのは、setで、親レベルの要素のnotifyを長さ属性の設定でトリガします。なぜ父のクラスの元素のnotifyを使いますか?配列の長さを設定している場合、その時点でのtarget\key\valueはそれぞれ[]\length*、この時、配列の依存性は収集されていません。あなたのwatchは配列自体ではありません。この時は通過するしかないです。  target.__parent__.__dep__.notify(keyBefore) は、親レベルの収集をトリガし、データ変化の検出を完了する。配列の長さを変えていないものについては、ここでのやり方は、直接的なものではありません。 ターゲットparent_._dep_.notify(keyBefore)は依存を触発しますが、深刻な問題があります。実際に更新されたデータは最新のものではありません。この場所はまだいい方法が思いつかないです。ご検討ください。
    Dep
    Dep.js
    
    let uid = 0;
    export default class Dep {
      constructor () {
        this.subs = {};
        this.id = uid++;
      }
      addSub(prop, sub) {
        this.subs[prop] = this.subs[prop] || [];
        this.subs[prop].push(sub);
      }
      removeSub(prop, sub) {
        this.remove(this.subs[prop] || [], sub);
      }
      depend(prop) {
        if (Dep.target) {
          //         
          Dep.target.addDep(prop, this)
        }
      }
      notify(prop) {
        const subs = (this.subs[prop] || []).slice();
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update();
        }
      }
      remove(arr, item) {
        if (arr.length) {
          const index = arr.indexOf(item);
          if (index > -1) {
            return arr.splice(index, 1);
          }
        }
      }
    }
    Dep.target = null
    const targetStack = []
    export function pushTarget (_target) {
      if (Dep.target) targetStack.push(Dep.target)
      Dep.target = _target
    }
    export function popTarget () {
      Dep.target = targetStack.pop()
    }
    depにpropを追加して、タイプのバインディングを実現します。なぜそうしますか?proxyエージェントを使用すると、wahcterオブジェクトのいくつかの要素が同時に存在する場合、これらの依存性は実行されます。したがって、key値バインディングによりイベントを観察し、トリガすると、オブジェクトの正確なトリガが完了します。
    ウォッチ、utils
    
    import { parsePath } from './utils';
    import { pushTarget, popTarget } from './dep'
    export default class Watcher {
      constructor(vm, expOrFn, cb) {
        // dep id  
        this.depIds = new Set();
        this.vm = vm;
        this.getter = parsePath(expOrFn);
        this.cb = cb;
        this.value = this.get();
      }
      get () {
        pushTarget(this);
        let value = this.getter.call(this.vm, this.vm);
        popTarget();
        return value;
      }
      update() {
        const oldValue = this.value;
        this.value = this.get();
        this.cb.call(this.vm, this.value, oldValue);
      }
      addDep (prop, dep) {
        const id = dep.id;
        if (!this.depIds.has(id)) {
          this.depIds.add(id);
          dep.addSub(prop, this);
        }
      }
    }
    utils.js
    
    /**
     *       
     */
    const bailRE = /[^\w.$]/;
    export function parsePath (path) {
      if (bailRE.test(path)) {
        return;
      }
      const segments = path.split('.');
      return function (obj) {
        for (let i = 0; i < segments.length; i++) {
          if (!obj) return;
          obj = obj[segments[i]];
        }
        return obj;
      };
    }
    /**
     * Define a property.
     */
    export function def (obj, key, val, enumerable) {
      Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
      })
    }
    /**
     * Quick object check - this is primarily used to tell
     * Objects from primitive values when we know the value
     * is a JSON-compliant type.
     */
    export function isObject (obj) {
      return obj !== null && typeof obj === 'object'
    }
    /**
     * Check whether an object has the property.
     */
    const hasOwnProperty = Object.prototype.hasOwnProperty
    export function hasOwn (obj, key) {
     return hasOwnProperty.call(obj, key)
    }
    Utils.js/Watchers.jsはVue 2.xに似ています。ここではあまり紹介しません。
    テストします
    test.js
    
    import Observer from './observer';
    import Watcher from './watcher';
    let data = {
      name: 'lijincai',
      password: '***********',
      address: {
        home: '       ',
      },
      list: [{
        name: 'lijincai',
        password: 'you know it Object',
      }], 
    };
    const newData = new Observer(data);
    let index = 0;
    const watcherName = new Watcher(newData, 'value.name', (newValue, oldValue) => {
      console.log(`${index++}: name newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherPassword = new Watcher(newData, 'value.password', (newValue, oldValue) => {
      console.log(`${index++}: password newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherAddress = new Watcher(newData, 'value.address', (newValue, oldValue) => {
      console.log(`${index++}: address newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherAddressHome = new Watcher(newData, 'value.address.home', (newValue, oldValue) => {
      console.log(`${index++}: address.home newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherAddProp = new Watcher(newData, 'value.addProp', (newValue, oldValue) => {
      console.log(`${index++}: addProp newValue:`, newValue, ', oldValue:', oldValue);
    });
    const watcherDataObject = new Watcher(newData, 'value.list', (newValue, oldValue) => {
      console.log(`${index++}: newValue:`, newValue, ', oldValue:', oldValue);
    });
    newData.value.name = 'resetName';
    newData.value.password = 'resetPassword';
    newData.value.name = 'hello world name';
    newData.value.password = 'hello world password';
    newData.value.address.home = 'hello home';
    newData.value.address.home = 'hello home2';
    newData.value.addProp = 'hello addProp';
    newData.value.addProp ={
      name: 'ceshi',
    };
    newData.value.addProp.name = 'ceshi2';
    newData.value.list.push('1');
    newData.value.list.splice(0, 1);
    newData.value.list.sort();
    newData.value.list.reverse();
    newData.value.list.push('1');
    newData.value.list.unshift({name: 'nihao'});
    newData.value.list[0] = {
      name: 'lijincai',
      password: 'you know it Array',
    };
    newData.value.list[0].name = 'you know it array after';
    newData.value.list.pop();
    newData.value.list.shift();
    newData.value.list.length = 1;
    対象、配列を使って私達のES 6 Proxy検査をテストします。
    
    20:17:44.725 index.js?afc7:20 0: name newValue: resetName , oldValue: lijincai
    20:17:44.725 index.js?afc7:24 1: password newValue: resetPassword , oldValue: ***********
    20:17:44.725 index.js?afc7:20 2: name newValue: hello world name , oldValue: resetName
    20:17:44.725 index.js?afc7:24 3: password newValue: hello world password , oldValue: resetPassword
    20:17:44.726 index.js?afc7:32 4: address.home newValue: hello home , oldValue:        
    20:17:44.726 index.js?afc7:32 5: address.home newValue: hello home2 , oldValue: hello home
    20:17:44.726 index.js?afc7:36 6: addProp newValue: hello addProp , oldValue: undefined
    20:17:44.727 index.js?afc7:36 7: addProp newValue: Proxy {name: "ceshi", __dep__: Dep, __parent__: {…}} , oldValue: hello addProp
    20:17:44.727 index.js?afc7:40 0: newValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.728 index.js?afc7:40 1: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.729 index.js?afc7:40 2: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.731 index.js?afc7:40 3: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.734 index.js?afc7:40 4: newValue: Proxy {0: "1", 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", 1: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.735 index.js?afc7:40 5: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.735 index.js?afc7:40 6: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.736 index.js?afc7:40 7: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.737 index.js?afc7:40 8: newValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.738 index.js?afc7:40 9: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    20:17:44.738 index.js?afc7:40 10: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
    ES 6 Proxyを見て、Object/Arayの検出を実現しました。まだ問題がありますが、基本的な検出変化の機能は全部備えました。
    締め括りをつける
    以上、小编が绍介したES 6 ProxyがVueの変化検出问题を実现しました。皆さんに助けてほしいです。もし何か疑问があれば、メッセージをください。小编はすぐに返事します。ここでも私たちのサイトを応援してくれてありがとうございます。
    本文があなたのためになると思ったら、転載を歓迎します。出所を明記してください。ありがとうございます。