レスポンシブ原則のソースコード分析のVue解釈

レスポンシブ原則のソースコード分析のVue解釈

まず写真を見て、全体的なプロセスと何をすべきかを理解してください

初期化

新しい Vue が初期化されると、コンポーネントのプロパティとデータが初期化されます。この記事では主に応答性について紹介するため、他の部分については詳しく説明しません。ソースコードを見てみましょう。

ソースコードアドレス: src/core/instance/init.js - 行 15

エクスポート関数 initMixin (Vue: Class<Component>) {
  // プロトタイプに_initメソッドを追加します Vue.prototype._init = function (options?: Object) {
    ...
    vm._self = vm
    initLifecycle(vm) // インスタンスのプロパティとデータを初期化します: $parent、$children、$refs、$root、_watcher... など。 initEvents(vm) // イベントを初期化します: $on、$off、$emit、$once
    initRender(vm) // レンダリングを初期化: render、mixin
    callHook(vm, 'beforeCreate') // ライフサイクルフック関数を呼び出す initInjections(vm) // injectを初期化する
    initState(vm) // コンポーネントデータを初期化する: props、data、methods、watch、computed
    initProvide(vm) // provideを初期化する
    callHook(vm, 'created') // ライフサイクル フック関数を呼び出します...
  }
}

ここでは初期化のために多くのメソッドが呼び出され、それぞれが異なる処理を実行します。応答性は主にコンポーネント内のデータ プロパティとデータを参照します。この部分は initState() メソッド内にあるため、このメソッドのソース コードを開いて確認してみましょう。

初期化状態()

ソースコードアドレス: src/core/instance/state.js - 行 49

エクスポート関数 initState (vm: コンポーネント) {
  vm._watchers = []
  定数opts = vm.$options
  // プロパティを初期化する
  if (opts.props) initProps(vm, opts.props)
  // 初期化メソッド
  if (opts.methods) initMethods(vm, opts.methods)
  // データを初期化する 
  if (opts.data) {
    初期化データ(vm)
  } それ以外 {
    // データがない場合、デフォルト値は空のオブジェクトとなり、observe(vm._data = {}, true /* asRootData */) となります。
  }
  // 計算結果を初期化する
  if (opts.computed) initComputed(vm, opts.computed)
  // ウォッチを初期化する
  opts.watch の場合、opts.watch は nativeWatch と等しくなります。
    initWatch(vm, opts.watch)
  }
}

ここでも、一連の初期化メソッドが呼び出されます。早速、レスポンシブデータに関連するもの、つまりinitProps()、initData()、observe()を取り上げます。
一つずつ選択し続け、応答性のプロセス全体を理解します。

初期化プロパティ()

ソースコードアドレス: src/core/instance/state.js - 行 65

ここで行われる主な作業は次のとおりです。

  • 親コンポーネントから渡されたpropsリストを走査する
  • 各属性の名前、タイプ、デフォルト属性などを確認します。問題がなければ、defineReactiveを呼び出してレスポンシブに設定します。
  • 次にproxy()を使用して、プロパティを現在のインスタンスにプロキシします。たとえば、vm._props.xxをvm.xxに変更すると、次のようになります。
関数 initProps (vm: コンポーネント、propsOptions: オブジェクト) {
  // 親コンポーネントは子コンポーネントにプロパティを渡します
  定数 propsData = vm.$options.propsData || {}
  // 変換後の最終プロパティ
  定数プロパティ = vm._props = {}
  //props のキーを保存します。props の値が空の場合でも、キーはそこに含まれます。const keys = vm.$options._propKeys = []
  定数isRoot = !vm.$parent
  // 非ルートインスタンスのプロパティを変換する
  ルートの場合
    監視の切り替え(false)
  }
  for (propsOptions 内の定数キー) {
    keys.push(キー)
    // プロパティのタイプ、デフォルトの属性などをチェックします。const value = validateProp(key, propsOptions, propsData, vm)
    // 非本番環境の場合 if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(キー)
      if (isReservedAttribute(ハイフン付きキー) ||
          config.isReservedAttr(ハイフン付きキー)) {
        警告(`hyphenatedKey は予約済みのプロパティであり、コンポーネント プロパティとして使用できません`)
      }
      // props をレスポンシブに設定する defineReactive(props, key, value, () => {
        // ユーザーがプロパティを変更した場合に警告する if (!isRoot && !isUpdatingChildComponent) {
          警告(`propsを直接変更しないでください`)
        }
      })
    } それ以外 {
      // props をレスポンシブに設定する defineReactive(props, key, value)
    }
    // デフォルトの vm にないプロパティをインスタンスにプロキシします // これにより、vm._props.xx に vm.xx 経由でアクセスできるようになります if (!(key in vm)) {
      プロキシ(vm、`_props`、キー)
    }
  }
  監視の切り替え(true)
}

初期化データ()

ソースコードアドレス: src/core/instance/state.js - 行 113

ここで行われる主な作業は次のとおりです。

  • データを初期化し、キ​​ーを設定します
  • キーコレクションを走査して、props 内のプロパティ名または methods 内のメソッド名と重複する名前があるかどうかを判断します。
  • 問題がなければ、proxy() を介してデータ内の各属性を現在のインスタンスにプロキシし、this.xx を介してアクセスできます。
  • 最後に、observeを呼び出してデータ全体を監視します。
関数 initData (vm: コンポーネント) {
  // 現在のインスタンスのデータを取得する 
  データ = vm.$options.data とします
  // データの型を決定する data = vm._data = typeof data === 'function'
    ? getData(データ、vm)
    : データ || {}
  if (!isPlainObject(データ)) {
    データ = {}
    process.env.NODE_ENV !== 'production' && warn(`データ関数はオブジェクトを返す必要があります`)
  }
  // 現在のインスタンスのデータ属性名セットを取得します。const keys = Object.keys(data)
  // 現在のインスタンスのプロパティを取得します 
  定数プロパティ = vm.$options.props
  // 現在のインスタンスのメソッドオブジェクトを取得します。const methods = vm.$options.methods
  i = キーの長さとする
  (i--) {
    定数キー = キー[i]
    // 非本番環境では、methods 内のメソッドが props 内に存在するかどうかを判定します if (process.env.NODE_ENV !== 'production') {
      if (メソッド && hasOwn(メソッド、キー)) {
        警告(`メソッド メソッドは繰り返し宣言できません`)
      }
    }
    // 非本番環境では、データ内の属性が props 内に存在するかどうかを判定します。if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(`プロパティを繰り返し宣言することはできません`)
    } そうでなければ (!isReserved(key)) {
      // 名前が同じでない場合は、vm にプロキシします // vm._data.xx は vm.xx を介して proxy(vm, `_data`, key) にアクセスできます
    }
  }
  // データをリッスンする
  観察(データ、true /* asRootData */)
}

観察する()

ソースコードアドレス: src/core/observer/index.js - 行 110

このメソッドは主にデータにリスナーを追加するために使用されます。

ここで行われる主な作業は次のとおりです。

  • vnodeオブジェクト型または参照型でない場合は、そのまま終了します。
  • それ以外の場合は、オブザーバーが追加されていないデータにオブザーバー、つまりリスナーを追加します。
エクスポート関数 observe (値: any、asRootData: ?boolean): Observer | void {
  // 'object' 型または vnode 型でない場合は、直接戻ります if (!isObject(value) || value instanceof VNode) {
    戻る
  }
  ob: オブザーバー | void
  // キャッシュされたオブジェクトを使用する if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = 値.__ob__
  } それ以外の場合 (
    観察すべき &&
    !isServerRendering() &&
    (Array.isArray(値) || isPlainObject(値)) &&
    Object.isExtensible(値) &&
    !値._isVue
  ){
    // リスナーを作成する ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  戻り値
}

オブザーバー

ソースコードアドレス: src/core/observer/index.js - 行 37

これは通常のデータを観測可能なデータに変換するクラスです。

ここで行われる主な作業は次のとおりです。

  • 繰り返し操作を避けるために、現在の値をレスポンシブ属性としてマークします。
  • 次にデータ型を決定します
    • オブジェクトの場合は、オブジェクトを反復処理し、defineReactive() を呼び出してレスポンシブなオブジェクトを作成します。
    • 配列の場合は、配列を反復処理し、observe() を呼び出して各要素を監視します。
エクスポートクラスObserver {
  値: 任意;
  dep: 削除;
  vmCount: number; // ルートオブジェクトコンストラクタ上のvmの数(値: 任意){
    this.value = 値
    this.dep = 新しい Dep()
    this.vmCount = 0
    // value に __ob__ 属性を追加します。この属性の値は value の Observe インスタンスです。 // これは、応答可能になったことを示します。目的は、オブジェクトをトラバースするときに直接スキップして、繰り返し操作を回避することです。 def(value, '__ob__', this)
    // 型判定 if (Array.isArray(value)) {
      // 配列に__proty__があるかどうかを判定する
      (hasProto) の場合 {
        // ある場合は、配列メソッド protoAugment(value, arrayMethods) を書き換えます。
      } それ以外 {
        // そうでない場合は、def を通じてプロパティ値を定義します。つまり、Object.defineProperty copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(値)
    } それ以外 {
      this.walk(値)
    }
  }
  // オブジェクト型の場合 walk (obj: Object) {
    定数キー = Object.keys(obj)
    // オブジェクトのすべてのプロパティを走査し、レスポンシブなオブジェクトに変換します。双方向バインディングを実現するために、ゲッターとセッターを動的に追加します for (let i = 0; i < keys.length; i++) {
      定義Reactive(obj, キー[i])
    }
  }
  //ObserveArray (items: Array<any>) {
    // 配列を走査し、各要素を監視します for (let i = 0, l = items.length; i < l; i++) {
      観察(アイテム[i])
    }
  }
}

リアクティブを定義する()

ソースコードアドレス: src/core/observer/index.js - 行 135

このメソッドの目的は、レスポンシブオブジェクトを定義することです

ここで行われる主な作業は次のとおりです。

  • まずdepインスタンスを初期化する
  • オブジェクトの場合は、構造がどれだけ深くネストされていても応答性のあるオブジェクトになることができるように、observe と listen を再帰的に呼び出します。
  • 次に、Object.defineProperty()を呼び出して、オブジェクトプロパティのゲッターとゲッターをハイジャックします。
  • 取得時にゲッターがトリガーされると、dep.depend()を呼び出してオブザーバーを依存配列サブ、つまり依存コレクションにプッシュします。
  • 更新された場合、トリガーセッターは次のことを実行します。
    • 新しい値は変更されていないか、セッター プロパティがありません。
    • 新しい値がオブジェクトの場合は、observe()を呼び出して再帰的に監視します。
    • 次に、dep.notify()を呼び出して更新を送信します。
エクスポート関数defineReactive(
  obj: オブジェクト、
  キー: 文字列、
  値: 任意、
  customSetter?: ?関数、
  浅い?: ブール値
){

  // depインスタンスを作成する const dep = new Dep()
  // オブジェクトのプロパティ記述子を取得します。const property = Object.getOwnPropertyDescriptor(obj, key)
  if (プロパティ && property.configurable === false) {
    戻る
  }
  // カスタムゲッターとセッターを取得する
  const ゲッター = プロパティ && プロパティ.get
  const セッター = プロパティ && プロパティ.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[キー]
  }
  // val がオブジェクトの場合は、再帰的に observe します // ​​observe を再帰的に呼び出すと、オブジェクト構造がどれだけ深くネストされていても、応答性の高いオブジェクトになることが保証されます let childOb = !shallow && observe(val)
  //オブジェクトプロパティのゲッターとセッターをインターセプトする
  Object.defineProperty(obj, キー, {
    列挙可能: true、
    設定可能: true、
    //インターセプトゲッター、値が取得されたときに関数がトリガーされます get: function reactiveGetter () {
      定数値 = getter ? getter.call(obj) : val
      // 依存関係の収集を実行します // レンダリングウォッチャーを初期化するときに、双方向バインディングを必要とするオブジェクトにアクセスし、それによって get 関数をトリガーします if (Dep.target) {
        依存()
        if (childOb) {
          childOb.dep.depend()
          Array.isArray(値)の場合{
            依存配列(値)
          }
        }
      }
      戻り値
    },
    //インターセプトセッター、値が変わると関数がトリガーされる set: function reactiveSetter (newVal) {
      定数値 = getter ? getter.call(obj) : val
      // 変更が発生したかどうかを判定します if (newVal === value || (newVal !== newVal && value !== value)) {
        戻る
      }
      process.env.NODE_ENV !== 'production' && customSetter の場合 {
        カスタムセッター()
      }
      // セッターなしのアクセサプロパティ if (getter && !setter) return
      if (セッター) {
        セッター呼び出し(obj, newVal)
      } それ以外 {
        val = 新しい値
      }
      // 新しい値がオブジェクトの場合は、再帰的に観察します。childOb = !shallow && observe(newVal)
      // 更新をディスパッチする dep.notify()
    }
  })
}

前述の通り、依存関係の収集は dep.depend を通じて行われます。Dep は getter 依存関係収集全体の中核であると言えます。

依存関係の収集

依存関係収集の核となるのはDepであり、Watcherと切り離すことはできません。

デップ

ソースコードアドレス: src/core/observer/dep.js

これは実際にWatcherを管理するクラスです

まず、依存関係、つまりオブザーバーを格納するためのサブ配列を初期化します。このデータに依存するものはすべてこの配列に格納され、追加、削除、更新の通知などを行ういくつかのメソッドを定義します。

さらに、静的属性ターゲットがあり、これはグローバル ウォッチャーです。つまり、同時に存在できるグローバル ウォッチャーは 1 つだけであることを意味します。

uid = 0 とする
デフォルトクラス Dep をエクスポートします {
  静的ターゲット: ?Watcher;
  id: 番号;
  サブ: Array<Watcher>;
  コンストラクタ(){
    this.id = uid++
    this.subs = []
  }
  // オブザーバーを追加する addSub (sub: Watcher) {
    this.subs.push(サブ)
  }
  // オブザーバーを削除します。removeSub (sub: Watcher) {
    削除(this.subs, sub)
  }
  依存する() {
    if (依存ターゲット) {
      //Watcher の addDep 関数を呼び出す Dep.target.addDep(this)
    }
  }
  // 更新を配布する(次のセクションで説明)
  通知(){
    ...
  }
}
// 一度に使用されるオブザーバーは 1 つだけなので、オブザーバーを割り当てます。Dep.target = null
定数ターゲットスタック = []

エクスポート関数pushTarget(ターゲット:?Watcher){
  ターゲットスタックをプッシュします。
  目標到達目標 = 目標
}

エクスポート関数popTarget(){
  ターゲットスタックのポップ()
  依存関係ターゲット = targetStack[targetStack.length - 1]
}

ウォッチャー

ソースコードアドレス: src/core/observer/watcher.js

Watcher もクラスであり、オブザーバー (サブスクライバー) とも呼ばれます。ここで行われる作業は非常に複雑で、レンダリングとコンパイルも接続します。

まずソース コードを見てから、依存関係の収集プロセス全体を見てみましょう。

uid = 0 とする
デフォルトクラスWatcherをエクスポートします{
  ...
  コンストラクタ(
    vm: コンポーネント、
    expOrFn: 文字列 | 関数、
    cb: 機能、
    オプション?: ?オブジェクト、
    isRenderWatcher?: ブール値
  ){
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = これ
    }
    vm._watchers.push(これ)
    // Watcherインスタンスが保持するDepインスタンスの配列 this.deps = []
    this.newDeps = []
    this.depIds = 新しいSet()
    this.newDepIds = 新しいSet()
    this.value = this.lazy
      ? 未定義
      : this.get()
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } それ以外 {
      this.getter = parsePath(expOrFn)
    }
  }
  得る () 
    // この関数はウォッチャーをキャッシュするために使用されます
    // コンポーネントにはネストされたコンポーネントが含まれているため、親コンポーネントのウォッチャーを復元する必要があります
    pushTarget(これ)
    値を与える
    定数 vm = this.vm
    試す {
      // コールバック関数、つまり upcateComponent を呼び出して、双方向バインディングを必要とするオブジェクトを評価し、依存関係コレクションをトリガーします。 value = this.getter.call(vm, vm)
    } キャッチ (e) {
      ...
    ついに
      // ディープモニタリング if (this.deep) {
        トラバース(値)
      }
      // ウォッチャーを復元する
      ポップターゲット()
      // 不要な依存関係をクリーンアップします this.cleanupDeps()
    }
    戻り値
  }
  // 依存関係の収集時にaddDep (dep: Dep)を呼び出す{
    定数 id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // 現在のウォッチャーを配列にプッシュします dep.addSub(this)
      }
    }
  }
  // 不要な依存関係をクリーンアップする(以下)
  クリーンアップDeps() {
    ...
  }
  // 更新がディスパッチされたときに呼び出されます (以下)
  アップデート () {
    ...
  }
  // ウォッチャーコールバックを実行する run () {
    ...
  }
  依存する() {
    i = this.deps.length とします
    (i--) {
      this.deps[i].depend()
    }
  }
}

補充:

独自のコンポーネントに記述されたウォッチが、新しい値と古い値の 2 つのパラメータを自動的に取得できるのはなぜですか?

つまり、watcher.run() でコールバックが実行され、新しい値と古い値が渡されます。

なぜ 2 つの Dep インスタンス配列を初期化するのですか?

Vueはデータ駆動型なので、データが変更されるたびに再レンダリングされます。つまり、vm.render()メソッドが再実行され、ゲッターが再度トリガーされます。そのため、新しく追加されたDepインスタンス配列newDepsと最後に追加されたインスタンス配列depsの2つの配列を使用してそれを表現します。

依存関係収集プロセス

初めてレンダリングしてマウントするときには、このようなロジックがあります

mountComponent ソースコード アドレス: src/core/instance/lifecycle.js - 行 141

エクスポート関数 mountComponent (...): コンポーネント {
  //ライフサイクルフック関数 callHook(vm, 'beforeMount') を呼び出す
  コンポーネントを更新します
  更新コンポーネント = () => {
    // _update を呼び出して、render によって返された仮想 DOM を実際の DOM にパッチ (つまり、Diff) します。これが最初のレンダリングです。vm._update(vm._render(), hydrating)
  }
  // 現在のコンポーネントインスタンスにオブザーバーを設定し、updateComponent 関数によって取得されたデータを監視します。以下は導入部です。new Watcher(vm, updateComponent, noop, {
    // 更新がトリガーされると、更新の前にbefore()が呼び出されます{
      // DOM がマウントされているかどうかを判定します。つまり、最初のレンダリングとアンマウント時には実行されません。if (vm._isMounted && !vm._isDestroyed) {
        //ライフサイクルフック関数 callHook(vm, 'beforeUpdate') を呼び出す
      }
    }
  }, true /* isRenderWatcher */)
  // 古いvnodeがないので、これが最初のレンダリングであることを示します。if (vm.$vnode == null) {
    vm._isMounted = true
    //ライフサイクルフック関数 callHook(vm, 'mounted') を呼び出す
  }
  戻り値
}

依存関係のコレクション:

  • マウントする前に、レンダリングウォッチャーがインスタンス化され、ウォッチャーコンストラクタに入るときにthis.get()メソッドが実行されます。
  • 次にpushTarget(this)が実行され、Dep.targetが現在のレンダリングウォッチャーに割り当てられ、スタックにプッシュされます(回復用)。
  • 次に、上記の updateComponent() 関数である this.getter.call(vm, vm) を実行し、vm._update(vm._render(), hydrating) を実行します。
  • 次に、vm._render() を実行してレンダリング vnode を生成します。このプロセス中に、vm 上のデータにアクセスし、データ オブジェクトのゲッターがトリガーされます。
  • 各オブジェクト値ゲッターには dep があります。ゲッターがトリガーされると、dep.depend() メソッドが呼び出され、Dep.target.addDep(this) も実行されます。
  • そして、ここで何らかの判断が行われ、同じデータが複数回追加されないようにし、その後、適格なデータがサブにプッシュされます。この時点で、依存関係の収集は完了していますが、まだ終了ではありません。オブジェクトの場合は、すべてのサブ項目のゲッターが再帰的にトリガーされ、Dep.target の状態を復元する必要があります。

サブスクリプションを削除する

サブスクリプションを削除するには、 cleanupDeps() メソッドを呼び出します。たとえば、テンプレートに v-if がある場合、条件を満たすテンプレート a 内の依存関係を収集します。条件が変化するとテンプレート b が表示され、テンプレート a は非表示になります。この時点で、依存関係を削除する必要があります

ここで行われる主な作業は次のとおりです。

  • まず、前回追加したインスタンス配列 deps を走査し、dep.subs 配列内の Watcher のサブスクリプションを削除します。
  • 次に、newDepIdsとdepIds、newDepsとdepsを入れ替えます。
  • 次にnewDepIdsとnewDepsをクリアします
// 不要な依存関係をクリーンアップする cleanupDeps () {
    i = this.deps.length とします
    (i--) {
      定数 dep = this.deps[i]
      (!this.newDepIds.has(dep.id))の場合{
        dep.removeSub(これ)
      }
    }
    tmp = this.depIds とします
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

アップデートを配布する

通知()

セッターがトリガーされると、dep.notify() が呼び出され、すべてのサブスクライバーに更新を配布するように通知します。

通知(){
    const subs = this.subs.slice()
    process.env.NODE_ENV !== 'production' && !config.async の場合 {
      // 非同期でない場合は、正しいトリガーを確実に実行するためにソートが必要です。subs.sort((a, b) => a.id - b.id)
    }
    // すべてのウォッチャーインスタンス配列を走査します for (let i = 0, l = subs.length; i < l; i++) {
      // 更新をトリガーする subs[i].update()
    }
  }

アップデート()

更新がトリガーされたときに呼び出されます

  アップデート () {
    if (this.lazy) {
      this.dirty = true
    } それ以外の場合は (this.sync) {
      この.run()
    } それ以外 {
      //コンポーネントデータの更新はここに行われます queueWatcher(this)
    }
  }

キューウォッチャー()

ソースコードアドレス: src/core/observer/scheduler.js - 行 164

これはキューであり、Vue が更新を配布する際の最適化ポイントでもあります。つまり、データが変更されるたびにウォッチャー コールバックがトリガーされるのではなく、これらのウォッチャーがすべてキューに追加され、nextTick の後に実行されます。

これは次のセクションの flushSchedulerQueue() のロジックと重複するため、一緒に理解する必要があります。

主なタスクは次のとおりです。

  • まず、hasオブジェクトを使用してIDを見つけ、同じウォッチャーが1回だけプッシュすることを確認します。
  • ウォッチャーの実行中に新しいウォッチャーが挿入された場合、ここに来て、後ろから前に向かって検索し、挿入するIDが現在のキューのIDよりも大きい最初の位置を見つけてキューに挿入します。これにより、キューの長さが変わります。
  • 最後に、待機することで、 nextTick が 1 回だけ呼び出されることを保証します。
エクスポート関数 queueWatcher (watcher: Watcher) {
  // ウォッチャーIDを取得する
  定数 id = ウォッチャー.id
  // 現在のIDのウォッチャーがプッシュされているかどうかを確認します if (has[id] == null) {
    has[id] = true
    if (!フラッシュ) {
      // まずここに入力します queue.push(watcher)
    } それ以外 {
      // 以下のflushSchedulerQueueを実行すると、新しく配布された更新があればここに入り、新しいウォッチャーを挿入します。以下は導入です。let i = queue.length - 1
      i > インデックス && queue[i].id > watcher.id の間 {
        私 - 
      }
      キュー.splice(i + 1, 0, ウォッチャー)
    }
    // 最初にここに入ります if (!waiting) {
      待機 = 真
      process.env.NODE_ENV !== 'production' && !config.async の場合 {
        スケジューラキューをフラッシュする()
        戻る
      }
      // 各更新ディスパッチによってレンダリングが発生するため、すべてのウォッチャーを nextTick に配置し、nextTick(flushSchedulerQueue) を呼び出します。
    }
  }
}

スケジューラキューをフラッシュする()

ソースコードアドレス: src/core/observer/scheduler.js - 行 71

ここで行われる主な作業は次のとおりです。

  • まずキューをソートします。ソート条件は 3 つあります。コメントを参照してください。
  • 次に、キューを走査し、対応する watcher.run() を実行します。実行後に新しいウォッチャーが追加され、上記の queueWatcher が再度実行される可能性があるため、キューの長さはトラバースされるたびに評価されることに注意してください。
関数flushSchedulerQueue() {
  現在のフラッシュタイムスタンプ = getNow()
  フラッシング = true
  ウォッチャー、ID

  // ID でソートします。次の条件が適用されます // 1. コンポーネントの更新は親から子の順序で行う必要があります。作成プロセスも最初に親、次に子であるためです // 2. コンポーネントに記述するウォッチャーはレンダリング ウォッチャーよりも優先されます
  // 3. 親コンポーネントのウォッチャーの実行中にコンポーネントが破棄された場合、このウォッチャーをスキップします
  キュー.ソート((a, b) => a.id - b.id)

  // キューの長さはトラバース中に変わる可能性があるため、キューの長さをキャッシュしないでください。 for (index = 0; index < queue.length; index++) {
    ウォッチャー = キュー[インデックス]
    if (watcher.before) {
      //beforeUpdateライフサイクルフック関数watcher.before()を実行する
    }
    id = ウォッチャーid
    has[id] = null
    // コンポーネントに記述したウォッチのコールバック関数を実行し、コンポーネントをレンダリングします watcher.run()
    // ループ更新をチェックして停止します。たとえば、ウォッチャープロセス中にオブジェクトが再割り当てされると、無限ループが発生します。if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      円形[id] = (円形[id] || 0) + 1
      (円形[id] > MAX_UPDATE_COUNT)の場合{
        警告(`無限ループ`)
        壊す
      }
    }
  }
  // 状態をリセットする前に、キューのコピーを保持します const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  スケジューラ状態をリセットする()
  // コンポーネントアクティベーションフックをアクティブ化する
  アクティブ化されたフックを呼び出します(アクティブ化されたキュー)
  // コンポーネント更新フックの呼び出しが更新されました
  更新されたフックを呼び出します(更新されたキュー)
}

更新されました()

最後に、更新します。ライフサイクルフック関数である updated は皆さんもよくご存知でしょう。

callUpdatedHooks()が上記で呼び出されると、ここに入り、更新された処理を実行します。

関数callUpdatedHooks(キュー){
  i = キューの長さとする
  (i--) {
    const ウォッチャー = キュー[i]
    定数 vm = ウォッチャー.vm
    if (vm._watcher === ウォッチャー && vm._isMounted && !vm._isDestroyed) {
      callHook(vm, '更新')
    }
  }
}

ここまでで、Vue2 のレスポンシブ原則プロセスのソースコードは基本的に分析されました。次に、上記のプロセスの欠点を紹介します。

定義プロパティの欠陥と解決策

Object.definePropertyを使用してレスポンシブオブジェクトを実装することにはまだいくつかの問題がある。

  • たとえば、オブジェクトに新しいプロパティを追加する場合、セッターはトリガーできません。
  • たとえば、配列要素の変更は検出できない

そしてこれらの問題に対して、Vue2にも対応する解決策がある。

Vue.set()

オブジェクトに新しいレスポンシブプロパティを追加する場合は、グローバルAPIであるVue.set()メソッドを使用できます。

ソースコードアドレス: src/core/observer/index.js - 行 201

set メソッドは次の 3 つのパラメータを受け入れます。

  • 対象: 配列または通常のオブジェクト
  • key: 配列の添え字またはオブジェクトのキー名を表します
  • val: 置き換えられる新しい値を表す

ここで行われる主な作業は次のとおりです。

  • まず、配列であり、添え字が正当であるかどうかを判断し、書き換えられたスプライスを直接使用して置き換えます。
  • オブジェクトであり、キーがターゲットに存在する場合は、値を置き換えます
  • __ob__がない場合、それはレスポンシブオブジェクトではないことを意味し、値は直接割り当てられ、返されます。
  • 最後に、新しいプロパティをレスポンシブにして更新をディスパッチします。
エクスポート関数セット (ターゲット: 配列<any> | オブジェクト、キー: any、値: any): any {
  (process.env.NODE_ENV !== 'production' && の場合
    (isUndef(ターゲット) || isPrimitive(ターゲット))
  ){
    警告(`未定義、null、またはプリミティブ値にリアクティブ プロパティを設定できません: ${(target: any)}`)
  }
  // 配列であり、添え字が有効である場合 if (Array.isArray(target) && isValidArrayIndex(key)) {
    ターゲットの長さ = Math.max(ターゲットの長さ、キー)
    // 直接置換するには splice を使用します。ここでの splice はネイティブではないため、検出される可能性があることに注意してください。詳細については、以下の target.splice(key, 1, val) を参照してください。
    戻り値
  }
  // これはオブジェクトであることを意味します // キーがターゲットに存在する場合は、直接割り当てられ、監視することもできます if (key in target && !(key in Object.prototype)) {
    ターゲット[キー] = 値
    戻り値
  }
  // target.__ob__ を取得します。
  const ob = (ターゲット: 任意).__ob__
  target._isVue の場合、ob と ob.vmCount は次のように記述します。
    process.env.NODE_ENV !== 'production' && 警告(
      'Vue インスタンスまたはそのルート $data にリアクティブ プロパティを追加しないでください ' +
      「実行時 - データ オプションで事前に宣言します。」
    )
    戻り値
  }
  // Observerで導入されたように、この属性が欠落している場合は、レスポンシブオブジェクトではないことを意味します if (!ob) {
    ターゲット[キー] = 値
    戻り値
  }
  // 次に、新しく追加された属性をレスポンシブにします。defineReactive(ob.value, key, val)
  // 手動で更新をディスパッチする ob.dep.notify()
  戻り値
}

配列メソッドのオーバーライド

ソースコードアドレス: src/core/observer/array.js

ここで行われる主な作業は次のとおりです。

  • 配列を変更するメソッドのリストを保存する
  • リスト内のメソッド(プッシュなど)を実行するときは、まず元のプッシュを保存し、その後レスポンシブな処理を行ってからこのメソッドを実行します。
// 配列のプロトタイプを取得します。const arrayProto = Array.prototype
// 配列プロトタイプを継承するオブジェクトを作成します。 export const arrayMethods = Object.create(arrayProto)
// 元の配列のメソッドリストを変更します const methodsToPatch = [
  '押す'、
  'ポップ'、
  'シフト'、
  'シフト解除'、
  'スプライス'、
  '選別'、
  '逆行する'
]
// 配列イベントを書き換えるmethodsToPatch.forEach(function (method) {
  //元のイベントを保存する const original = arrayProto[method]
  // レスポンシブオブジェクトを作成する def(arrayMethods, method, function mutator (...args) {
    const 結果 = original.apply(this, args)
    定数ob = this.__ob__
    挿入させる
    スイッチ(メソッド){
      ケース 'プッシュ':
      ケース 'unshift':
        挿入された = 引数
        壊す
      ケース 'スプライス':
        挿入 = args.slice(2)
        壊す
    }
    if (挿入済み) ob.observeArray(挿入済み)
    // 更新を配布する ob.dep.notify()
    // 必要な処理が完了したら、元のイベントを実行して結果を返します
  })
})

要約する

これで、Vue のレスポンシブ原則のソースコード分析の解釈に関するこの記事は終了です。Vue のレスポンシブ原則のソースコードに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • VUE 応答性原理の詳細な説明
  • レスポンシブ原則をシミュレートするための基礎コードの Vue 実装の例
  • Vue のレスポンシブ原則と双方向データの詳細な分析
  • レスポンシブ原則と Vue2.0/3.0 の違いについての簡単な分析
  • Vueのデータ応答性原則の詳細な説明
  • Vue の応答性原則の詳細な分析
  • Vue3のレスポンシブ原則の詳細な説明

<<:  docker CMD/ENTRYPOINT が sh スクリプトを実行する問題の解決策: not found/run.sh:

>>:  HTMLは実際にはいくつかの重要なタグを学ぶアプリケーションです

推薦する

HTML要素を非表示にするいくつかの方法

1. CSSを使用するコードをコピーコードは次のとおりです。スタイル="display:n...

WeChatアプレットトラック再生の実装と遭遇した落とし穴の詳細な説明

WeChat アプレットの軌跡再生では、主に線描画操作にポリラインを使用し、車の移動操作にマーカーを...

MySQL MyISAM と InnoDB の違い

違い: 1. InnoDB はトランザクションをサポートしていますが、MyISAM はサポートしてい...

htmlダウンロード機能の詳しい説明

新しいプロジェクトは基本的に終了しました。フロントエンドとバックエンドを分離して統合を完了したのは初...

MySQL データベースのインポートとエクスポートのデータ エラーの解決例の説明

データのエクスポートエラーを報告する 「secure_file_priv」のような変数を表示します。...

sqlite3 から mysql に移行するときに起こりうる問題のコレクション

簡単な説明適切な読者: モバイル開発sqlite3 データを mysql に移行する場合、多くの構文...

フロントエンドページのポップアップマスクはページのスクロールを禁止します

フロントエンド開発者がよく遭遇する問題は、ユーザーに情報を提示するためのポップアップ ウィンドウを作...

Alibaba Cloud ECS centos6.8 に MySql5.7 をインストールして設定するチュートリアル

Alibaba Cloud yum コマンドでのデフォルトの MySQL バージョンは 5.17**...

MySQL マルチバージョン同時実行制御 MVCC の実装

目次MVCCとはMVCC 実装MVCC はファントム リードを解決しますか? MVCCとはMVCC ...

MySQL監視ツールmysql-monitorの詳細な説明

1. 概要mysql-monitor MYSQL 監視ツール、最適化ツール、1 つの Java Sp...

WeChatアプレットが連携メニューを実現

最近はコース設計を実現するために、フロントエンドも少しやっています。今日はいくつかの機能を実現するた...

Docker を使用した Redis マスタースレーブレプリケーションの実践の詳細説明

目次1. 背景2. 操作手順3. Dockerをインストールする4. 主なサービス構成5. サービス...

VMware 仮想マシンで HTTP サービスを確立して分析する手順

1. xshell を使用して仮想マシンに接続するか、仮想マシンに直接コマンドを入力します。以下はx...

CSS3 @mediaの基本的な使い方のまとめ

//文法: @media mediatype and | not | only (メディア機能) ...