vueソースコードのデータ応答式原理
10655 ワード
vueの概要
漸進的なフレームワーク:フレームを階層化することです.最も核心的なのは、ビューレイヤをレンダリングし、外側にコンポーネントメカニズムを追加し、その上でルーティングメカニズムを追加し、状態管理を追加し、最外層の構築ツールを追加することです.階層化とは、最もコアなビューレイヤレンダリングでいくつかのニーズを開発したり、vueファミリーバケツで大規模なアプリケーションを開発したりすることができます.異なるレベルを選択するには、より独自のニーズを持つことができます.
データ傍受(Object)
ここでの関数
パッケージ化後、
依存を収集する方法
考えてみれば、データを観察する目的は、データの属性が変化したときに、そのデータを使用した場所を通知することです.
このテンプレートにはデータ
注意:Vue.js 2.0では、テンプレートがデータを使用するのはコンポーネントがデータを使用するのと同じであるため、データが変化すると、通知がコンポーネントに送信され、コンポーネント内部が仮想DOMで再レンダリングされる.
上記の問題では,まず依存を収集し,すなわちデータnameを用いた場所を収集し,その後属性が変化した場合に,以前に収集した依存ループを一度トリガすればよい.
まとめると、実は一言で、
どこに集めて
考えてみると、まず考えられるのは、現在の
ここでは,収集された依存性を格納するための配列
そして、
しかし、このように書くと、収集に依存するコードを
その後再改造下
誰かに頼って
上のコードでは、私たちが収集した依存は
誰を収集するか、言い換えれば、属性が変化した後、誰に通知するかです.
データを使用する場所を通知しますが、このデータを使用する場所は多く、タイプが異なり、テンプレートである可能性もあれば、ユーザーが書いた
今すぐ上の質問に答えることができます.誰を集めますか?
Watcherとは
このコードは、
どうやってこの機能を実現するのか考えてみましょう.このwatcherインスタンスをdataに追加するだけのようです.a.b.c属性のDepでいいです.そしてdata.a.b.cの値が変化した場合、Watcherに通知します.次に、Watcherはパラメータ内のこのコールバック関数を再実行する.
このコードは自分から
私は
これにより、
依存が
したがって、実際には、ユーザが実行する
ここでは、上のコードのparsePathが文字列の
これは複雑ではないことがわかります.まずkeypathを使います.配列に分割し、配列をループして1層ずつデータを読み、最後に手に入れたobjはkeypathで読みたいデータです.
すべてのkeyを再帰的に検出
現在、実際には変化検出機能を実現することができますが、前に紹介したコードはデータのいずれかの属性しか検出できません.データのすべての属性(サブ属性を含む)を検出したいので、1つの
上記のコードでは、通常の
次に、
最後に、
すなわち、我々が1つの
Objectに関する質問
いくつかの文法はデータが変化してもvue.jsもモニタリングできません.例えば、Objectに属性を追加したり削除したりします.
Es 6 proxy方式データ応答を傍受する方式
総括変化検出は検出データの変化であり、データが変化した場合、検出して通知を送信することができる.
ObjectはObjectを通過することができる.definePropertyは、属性をgetter/setterの形式に変換して変化を追跡します.データを読み込むとgetterがトリガーされ、データを変更するとsetterがトリガーされます.
getterでは携帯電話がどのような依存関係でデータを使用しているのか.setterがトリガーされると、getterで収集された依存データが変化したことを通知します.
依存ストレージを収集する場所は、依存を収集したり、依存を削除したり、依存メッセージを送信したりするためのDepを作成することです.
依存はwatcherであり,watcherがトリガしたgetterだけが依存を収集し,どのwatcherがgetterをトリガするか,どのwatcherをDepに収集する.データが変化すると、依存リストがループし、すべてのwatcherが通知されます.
watcherの原理は、まず自分をグローバルで一意の指定位置(例えばwindow.target)に設定し、データを読み出すことです.データを読み込んだので、このデータのgetterがトリガーされます.次にgetterでグローバル唯一のwindowから.targetは、現在データを読み出しているwatcherを読み出し、このwatcherをDepに収集する.
また、Object内のすべてのデータを応答式に変換する役割を果たすObserveクラスを作成します.
Data、Observe、Dep、Watcherの関係:DataはObserveからgetter/setterに変換することで変化を追跡する.外部がwatcherを介してデータを読み出すと、getterがトリガーされ、watcherが依存に追加されます.データが変化するとsetterがトリガーされ、Depの依存(watcher)に通知が送信されます.watcherは通知を受信すると外部に通知を送信し,外部に変化通知するとビュー更新をトリガーしたり,ユーザのコールバック関数をトリガーしたりする可能性がある.
漸進的なフレームワーク:フレームを階層化することです.最も核心的なのは、ビューレイヤをレンダリングし、外側にコンポーネントメカニズムを追加し、その上でルーティングメカニズムを追加し、状態管理を追加し、最外層の構築ツールを追加することです.階層化とは、最もコアなビューレイヤレンダリングでいくつかのニーズを開発したり、vueファミリーバケツで大規模なアプリケーションを開発したりすることができます.異なるレベルを選択するには、より独自のニーズを持つことができます.
データ傍受(Object)
Object.defineProperty
とES6
を用いたProxy
と function defineReactive(data, key ,val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
return val
},
set: function(newVal) {
if(val === newVal) {
return;
}
val = newVal
}
})
}
ここでの関数
defineReactive
は、Object.defineProperty
をカプセル化するために使用される.関数の名前から,応答式データを定義する役割を果たすことがわかる.すなわち,この関数で変化追跡を行い,カプセル化後はdata
,key
とval
を伝達するだけでよい.パッケージ化後、
data
のkey
からデータを読み出すたびに、get
関数がトリガーされる.data
のkey
にデータを設定するたびに、set
関数がトリガーされます.依存を収集する方法
Object.defineProperty
をカプセル化するだけでは、実際には何の役にも立たず、本当に役に立つのは収集依存です.考えてみれば、データを観察する目的は、データの属性が変化したときに、そのデータを使用した場所を通知することです.
{{ name }}
このテンプレートにはデータ
name
が使用されているので、変更が発生した場合は、使用した場所に通知を送信します.注意:Vue.js 2.0では、テンプレートがデータを使用するのはコンポーネントがデータを使用するのと同じであるため、データが変化すると、通知がコンポーネントに送信され、コンポーネント内部が仮想DOMで再レンダリングされる.
上記の問題では,まず依存を収集し,すなわちデータnameを用いた場所を収集し,その後属性が変化した場合に,以前に収集した依存ループを一度トリガすればよい.
まとめると、実は一言で、
getter
で依存を収集し、setter
で依存をトリガする.どこに集めて
考えてみると、まず考えられるのは、現在の
key
の依存を格納するための配列です.依存が関数であると仮定し、key
に保存し、window.target
関数を少し改造することができます. function defineReactive(data, key, val) {
let dep = [];
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.push(window.target) //
return val
},
set(newVal) {
if(val === newVal) {
return;
}
//
for (let i = 0; i < dep.length; i++) {
dep[i](newVal, val)
}
val = newVal
}
})
}
ここでは,収集された依存性を格納するための配列
defineReactive
を追加した.そして、
dep
がトリガされると、収集された依存をトリガするためにset
がループされる.しかし、このように書くと、収集に依存するコードを
dep
クラスにカプセル化し、依存を管理するのに役立ちます.このクラスを使用すると、依存を収集したり、削除したり、依存に通知を送信したりすることができます.コードは次のとおりです. export default class Dep {
constructor() {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
removeSub (sub) {
remove(this.subs, sub)
}
depend () {
if (window.target) {
this.addSub(window.target)
}
}
notify() {
const subs = this.subs.slice();
for(let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
その後再改造下
Dep
: function defineReactive (data, key, val) {
let dep = new Dep() //
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend() //
return val
},
set: function (newVal) {
if(val === newVal){
return
}
val = newVal
dep.notify() //
}
})
}
誰かに頼って
上のコードでは、私たちが収集した依存は
defineReactive
ですが、それはいったい何ですか.私たちはいったい誰を集めますか?誰を収集するか、言い換えれば、属性が変化した後、誰に通知するかです.
データを使用する場所を通知しますが、このデータを使用する場所は多く、タイプが異なり、テンプレートである可能性もあれば、ユーザーが書いた
window.target
である可能性もあります.この場合、これらの状況を集中的に処理できるクラスを抽象化する必要があります.次に,収集に依存する段階でこのカプセル化されたクラスのインスタンスのみを収集し,通知も1つだけ通知する.次に、他の場所に通知する責任を負います.だから、抽象的なものはまず良い名前をつける必要があります.うん、それをwatch
と呼んでください.今すぐ上の質問に答えることができます.誰を集めますか?
Watcher
! Watcherとは
Watcher
は、データが変化したときに通知され、他の場所に通知される仲介者の役割です.Watcher
については、まず古典的な使い方を見てみましょう. // keypath
vm.$watch('a.b.c', function (newVal, oldVal) {
//
})
このコードは、
Watcher
属性が変化すると、2番目のパラメータの関数がトリガーされることを示す.どうやってこの機能を実現するのか考えてみましょう.このwatcherインスタンスをdataに追加するだけのようです.a.b.c属性のDepでいいです.そしてdata.a.b.cの値が変化した場合、Watcherに通知します.次に、Watcherはパラメータ内のこのコールバック関数を再実行する.
export default class Watcher {
constructor (vm, expOrFn, cb) {
this.vm = vm
// this.getter(), data.a.b.c
this.getter = parsePath(expOrFn)
this.cb = cb
this.value = this.get()
}
get() {
window.target = this
let value = this.getter.call(this.vm, this.vm)
window.target = undefined
return value
}
update () {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
このコードは自分から
data.a.b.c
のdata.a.b.c
に追加することができますが、不思議ではありませんか?私は
Dep
メソッドでget
をwindow.target
に設定したので、現在のthis
インスタンスであり、watcher
の値をもう一度読んでみると、必ずdata.a.b.c
がトリガーされます.getter
がトリガーされると、収集依存の論理がトリガーされます.収集依存については、前述したように、getter
から依存が読み込まれ、window.target
に追加されます.これにより、
Dep
にwindow.target
を割り当ててから値を読み、this
をトリガすれば、getter
をthis
のkeypath
にアクティブに追加することができる.不思議な感じはありますか?依存が
Dep
に注入されると、Dep
の値が変化するたびに、依存リスト内のすべての依存サイクルがdata.a.b.c
メソッド、すなわちupdate
のWatcher
メソッドをトリガする.一方、update
メソッドは、update
およびvalue
をパラメータに転送するパラメータのコールバック関数を実行します.したがって、実際には、ユーザが実行する
oldValue
であっても、テンプレートで使用されるvm.$watch('a.b.c', (value, oldValue) => {})
であっても、data
によって変化が必要か否かが通知される.ここでは、上のコードのparsePathが文字列の
Watcher
をどのように読み取ったのか、気になる人もいるかもしれません.次に、その実現原理をコードで説明します./**
*
*/
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
}
}
これは複雑ではないことがわかります.まずkeypathを使います.配列に分割し、配列をループして1層ずつデータを読み、最後に手に入れたobjはkeypathで読みたいデータです.
すべてのkeyを再帰的に検出
現在、実際には変化検出機能を実現することができますが、前に紹介したコードはデータのいずれかの属性しか検出できません.データのすべての属性(サブ属性を含む)を検出したいので、1つの
keypath
クラスをカプセル化します.このクラスの役割は、1つのデータ内のすべての属性(サブ属性を含む)をObserver
の形式に変換し、それらの変化を追跡することです. /**
* Observer object 。
* ,Observer object getter/setter
* ,
*/
export class Observer {
constructor (value) {
this.value = value
if (!Array.isArray(value)) {
this.walk(value)
}
}
/**
* walk getter/setter
* Object
*/
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
function defineReactive (data, key, val) {
// ,
if (typeof val === 'object') {
new Observer(val)
}
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend()
return val
},
set: function (newVal) {
if(val === newVal){
return
}
val = newVal
dep.notify()
}
})
}
上記のコードでは、通常の
getter/setter
を検出されたObserver
に変換するためにobject
クラスを定義した.次に、
object
タイプのデータのみがObject
を呼び出し、各属性をwalk
に変換して変化を検出する.最後に、
getter/setter
にdefineReactive
を追加してサブ属性を再帰し、new Observer(val)
のすべての属性(サブ属性を含む)をdata
の形式に変換して変化を検出することができる.getter/setter
の属性が変化すると、その属性に対応する依存が通知を受信する.すなわち、我々が1つの
data
をobject
に伝達すれば、このObserver
は応答式のobject
になる.Objectに関する質問
いくつかの文法はデータが変化してもvue.jsもモニタリングできません.例えば、Objectに属性を追加したり削除したりします.
Es 6 proxy方式データ応答を傍受する方式
let obj = {
a: 1,
b: 2,
c: 3
}
let reactive = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}`);
return Reflect.get(target, key, receiver)
}
set: function(target, key, receiver) {
console.log(`setting ${key}`);
return Reflect.set(target, key, receiver)
}
})
reactive.a // getting a // 1
reactive.a = 4 // setting a
reactive.a // getting a // 4
総括変化検出は検出データの変化であり、データが変化した場合、検出して通知を送信することができる.
ObjectはObjectを通過することができる.definePropertyは、属性をgetter/setterの形式に変換して変化を追跡します.データを読み込むとgetterがトリガーされ、データを変更するとsetterがトリガーされます.
getterでは携帯電話がどのような依存関係でデータを使用しているのか.setterがトリガーされると、getterで収集された依存データが変化したことを通知します.
依存ストレージを収集する場所は、依存を収集したり、依存を削除したり、依存メッセージを送信したりするためのDepを作成することです.
依存はwatcherであり,watcherがトリガしたgetterだけが依存を収集し,どのwatcherがgetterをトリガするか,どのwatcherをDepに収集する.データが変化すると、依存リストがループし、すべてのwatcherが通知されます.
watcherの原理は、まず自分をグローバルで一意の指定位置(例えばwindow.target)に設定し、データを読み出すことです.データを読み込んだので、このデータのgetterがトリガーされます.次にgetterでグローバル唯一のwindowから.targetは、現在データを読み出しているwatcherを読み出し、このwatcherをDepに収集する.
また、Object内のすべてのデータを応答式に変換する役割を果たすObserveクラスを作成します.
Data、Observe、Dep、Watcherの関係:DataはObserveからgetter/setterに変換することで変化を追跡する.外部がwatcherを介してデータを読み出すと、getterがトリガーされ、watcherが依存に追加されます.データが変化するとsetterがトリガーされ、Depの依存(watcher)に通知が送信されます.watcherは通知を受信すると外部に通知を送信し,外部に変化通知するとビュー更新をトリガーしたり,ユーザのコールバック関数をトリガーしたりする可能性がある.