[フロントエンド辞書] Vueの応答原理は実際には非常に理解しやすいです

序文

これは、一連の10のVue記事の3番目です。この記事では、Vueのコア機能の1つである応答性の原理について説明します。

レスポンシブを理解する方法

状態が変化すると、この状態に関連するトランザクションも即座に変化します。フロントエンドの観点からは、データの状態が変化すると、関連するDOMも変化します。データモデルは通常のJavaScriptオブジェクトです。そして、それらを変更すると、ビューが更新されます。

質問を投げる

Vueでの私たちの一般的な文章を見てみましょう:

<div id="app" @click="changeNum">

  {{ num }}

</div>

var app = new Vue({

  el: '#app',

  data: {

    num: 1

  },

  methods: {

    changeNum() {

      this.num = 2

    }

  }

})

この書き方は非常に一般的ですが、this.num = 2が実行されたときにビューが更新される理由を検討しましたか?この記事を通して、私はこの点を明確にするよう努めています。

Vueを使用しない場合、どのようにそれを達成する必要がありますか?

私の最初の考えは、次のように実装することでした。

let data = {

  num: 1

};

Object.defineProperty(data, 'num',{

  value: value,

  set: function( newVal ){

    document.getElementById('app').value = newVal;

  }

});

input.addEventListener('input', function(){

  data.num = 2;

});

このようにして、要素を大まかにクリックして、ビューを自動的に更新できます。

ここでは、Object.definePropertyを介してオブジェクトのアクセサープロパティを操作する必要があります。データの変更が監視されると、関連するDOMが操作されます。

ここでは、一般的なパターンである公開/サブスクライブパターンが使用されます。

オブザーバーモデルと公開/サブスクライブモデルを説明するために、一般的なフローチャートを作成しました。次のように:

[フロントエンド辞書] Vueの応答原理は実際には非常に理解しやすいです

注意深い学生は、私の大まかなプロセスとVueの使用の違いは、DOMを操作して自分で再レンダリングする必要があることに気付くでしょう。

Vueを使用する場合、このステップはVue内のコードによって処理されます。これが、Vueを使用するときにDOMを手動で操作する必要がない理由です。

Object.definePropertyについて前の記事で述べたので、ここでは繰り返しません。

Vueはどのように応答性を実現しますか

オブジェクトはObject.definePropertyを介してアクセサプロパティを操作できることがわかっています。つまり、オブジェクトにはgetterメソッドとsetterメソッドがあります。これが応答性の基礎です。

まず、非常に直感的なフローチャートを見てください。
[フロントエンド辞書] Vueの応答原理は実際には非常に理解しやすいです

initDataメソッド

Vueが初期化されると、その_init()メソッドはinitState(vm)メソッドを呼び出して実行します。initStateメソッドは、主に小道具、メソッド、データ、計算、wathcerなどのプロパティを初期化します。

ここでは、データ初期化のプロセスのより詳細な分析を行います。

function initData (vm: Component) {

  let data = vm.$options.data

  data = vm._data = typeof data === 'function'

    ? getData(data, vm)

    : data || {}

  if (!isPlainObject(data)) {

    ......

  }

  // proxy data on instance

  const keys = Object.keys(data)

  const props = vm.$options.props

  const methods = vm.$options.methods

  let i = keys.length

  while (i--) {

    const key = keys[i]

    ...... // 省略部分兼容代码,但不影响理解

    if (props && hasOwn(props, key)) {

      ......

    } else if (!isReserved(key)) {

      proxy(vm, `_data`, key)

    }

  }

  // observe data

  observe(data, true /* asRootData */)

}

データを初期化するinitDataの主なプロセスは、次の2つのことを行うことです。

  1. 各値vm._data。[key]をプロキシを介してvm。[key]にプロキシします。

  2. データ全体の変化を監視するためにobserveメソッドが呼び出され、データが応答(監視可能)になり、定義されたデータ戻り関数の対応する属性にvm._data。[key]からアクセスできます。

データハイジャック—観察する

この方法により、データの下にあるすべての属性が応答性(監視可能)になります。

// 给对象的属性添加 getter 和 setter,用于依赖收集和发布更新

export class Observer {

  value: any;

  dep: Dep;  

  vmCount: number; 

  constructor (value: any) {

    this.value = value

    // 实例化 Dep 对象

    this.dep = new Dep()

    this.vmCount = 0

    // 把自身实例添加到数据对象 value 的 __ob__ 属性上

    def(value, '__ob__', this)

    // value 是否为数组的不同调用

    if (Array.isArray(value)) {

      const augment = hasProto ? protoAugment : copyAugment

      augment(value, arrayMethods, arrayKeys)

      this.observeArray(value)

    } else {

      this.walk(value)

    }

  }

  // 取出所有属性遍历

  walk (obj: Object) {

    const keys = Object.keys(obj)

    for (let i = 0; i < keys.length; i++) {

      defineReactive(obj, keys[i])

    }

  }

  observeArray (items: Array<any>) {

    for (let i = 0, l = items.length; i < l; i++) {

      observe(items[i])

    }

  }

}

Object.definePropertyはdef関数にカプセル化されているため、console.log(data)は追加のob属性を見つけます。

すべての属性をトラバースするdefineReactiveメソッド

// 定义一个响应式对象的具体实现

export function defineReactive (

  obj: Object,

  key: string,

  val: any,

  customSetter?: ?Function,

  shallow?: boolean

) {

  const dep = new Dep()

  ..... // 省略部分兼容代码,但不影响理解

  let childOb = !shallow && observe(val)

  Object.defineProperty(obj, key, {

    enumerable: true,

    configurable: true,

    get: function reactiveGetter () {

      const value = getter ? getter.call(obj) : val

      if (Dep.target) {

        // 进行依赖收集

        dep.depend()

        if (childOb) {

          childOb.dep.depend()

          if (Array.isArray(value)) {

            dependArray(value)

          }

        }

      }

      return value

    },

    set: function reactiveSetter (newVal) {

      const value = getter ? getter.call(obj) : val

      ..... // 省略部分兼容代码,但不影响理解

      if (setter) {

        setter.call(obj, newVal)

      } else {

        val = newVal

      }

      // 对新的值进行监听

      childOb = !shallow && observe(newVal)

      // 通知所有订阅者,内部调用 watcher 的 update 方法 

      dep.notify()

    }

  })

}

defineReactiveメソッドは、最初にDepオブジェクトのインスタンスを初期化し、次に子オブジェクトのobserveメソッドを再帰的に呼び出して、すべての子プロパティもリアクティブオブジェクトになることができるようにします。そして、Object.definePropertyのgetterメソッドとsetterメソッドでdep関連のメソッドを呼び出します。

これは:

  1. getterメソッドによって実行される作業は、依存関係の収集です-dep.depend()

  2. setterメソッドによって行われる作業は、更新を投稿することです-dep.notify()

Depオブジェクトとの関係は無視できないことがわかりました。次に、Depオブジェクトを見ていきます。この部

ディスパッチセンターの部

前回の記事では、発行/サブスクライブモデルについて説明しました。発行者とサブスクライバーの前にディスパッチセンターがあります。ここでDepが果たす役割はディスパッチセンターであり、その主な役割は次のとおりです。

  1. サブスクライバーウォッチャーを収集し、ウォッチャーリストのサブに追加します

  2. 出版社からイベントを受け取る

  3. ターゲットの更新をサブスクライバーに通知し、サブスクライバーに独自の更新方法を実行させます

詳細なコードは次のとおりです。

// Dep 构造函数

export default class Dep {

  static target: ?Watcher;

  id: number;

  subs: Array<Watcher>;

  constructor () {

    this.id = uid++

    this.subs = []

  }

  // 向 dep 的观察者列表 subs 添加 Watcher

  addSub (sub: Watcher) {

    this.subs.push(sub)

  }

  // 从 dep 的观察者列表 subs 移除 Watcher

  removeSub (sub: Watcher) {

    remove(this.subs, sub)

  }

  // 进行依赖收集

  depend () {

    if (Dep.target) {

      Dep.target.addDep(this)

    }

  }

  // 通知所有订阅者,内部调用 watcher 的 update 方法

  notify () {

    const subs = this.subs.slice()

    for (let i = 0, l = subs.length; i < l; i++) {

      subs[i].update()

    }

  }

}

// Dep.target 是全局唯一的观察者,因为在任何时候只有一个观察者被处理。

Dep.target = null

// 待处理的观察者队列

const targetStack = []

export function pushTarget (_target: ?Watcher) {

  if (Dep.target) targetStack.push(Dep.target)

  Dep.target = _target

}

export function popTarget () {

  Dep.target = targetStack.pop()

}

DepはWatcherの管理として理解でき、DepとWatcherは密接に関連しています。したがって、Watcherの実装を確認する必要があります。

サブスクライバーウォッチャー

Watcherには多くのプロトタイプメソッドが定義されていますが、ここでは更新について簡単に説明し、3つのメソッドを取得します。

  // 为了方便理解,部分兼容代码已被我省去

  get () {

    // 设置需要处理的观察者

    pushTarget(this)

    const vm = this.vm

    let value = this.getter.call(vm, vm)

    // deep 是否为 true 的处理逻辑

    if (this.deep) {

      traverse(value)

    }

    // 将 Dep.target 指向栈顶的观察者,并将他从待处理的观察者队列中移除

    popTarget()

    // 执行依赖清空动作

    this.cleanupDeps()

    return value

  }

  update () {

    if (this.computed) {

      ...

    } else if (this.sync) { 

      // 标记为同步

      this.run()

    } else {      

      // 一般都是走这里,即异步批量更新:nextTick

      queueWatcher(this)

    }

  }

これはおそらくVueのリアクティブプロセスの場合です。興味のある方はソースコードをご覧ください。

最後に、次のフローチャートで確認します。
[フロントエンド辞書] Vueの応答原理は実際には非常に理解しやすいです

Vue関連記事の出力計画

最近、友達からVue関連の質問が寄せられているので、次はVue関連の記事を9つ出力します。更新は7〜10日で行います。

  1. [フロントエンド辞書] VuexがVueライフサイクルプロセスを注入します(完了)

  2. 【フロントエンド辞書】Vueソースコード学習に必要な知識予備(完成)

  3. [フロントエンド辞書] Vueの応答原理は実際には非常に理解しやすい(完全)

  4. [フロントエンド辞書]新旧のVNodeにパッチを適用するプロセス

  5. [フロントエンド辞書]機能コンポーネントの開発方法とnpmのアップロード方法

  6. [フロントエンド辞書]これらの側面からVueプロジェクトを最適化する

  7. [フロントエンド辞書] Vue-Router設計からのフロントエンドルーティングの開発について話します

  8. 【フロントエンド辞書】プロジェクトでWebpackを正しく使う方法

  9. 【フロントエンド辞書】Vueサーバーレンダリング

  10. 【フロントエンド辞書】AxiosとFetchの選び方

私の公式アカウントに注意を払うことをお勧めします。できるだけ早く最新の記事を受け取ることができます。
[フロントエンド辞書] Vueの応答原理は実際には非常に理解しやすいです
グループ通信に参加したい場合は、スマートロボットを追加して、自動的にグループに参加させることもできます。

[フロントエンド辞書] Vueの応答原理は実際には非常に理解しやすいです

おすすめ

転載: blog.51cto.com/15077552/2596430