Vue応答式の原理と簡単な実現

5852 ワード

vue原理では,データの観測,依存収集,ビューの更新をどのように実現するかが最も重要である.本稿ではObserver,Dep,Watcherの3つの簡単な実装について述べる.pub(publish)はパブリッシャー、sub(subscribe)はサブスクライバ、cb(callback)はコールバック関数を表します.この話が役に立つと思ったら、starを注文してください.
observerの実装
Observerの役割は簡単に言えばobjectオブジェクトの属性をすべてObjectにすることである.defineProperty()を定義することで、objectの属性を取得したり、属性を変更したりすると、get,setをトリガーしてデータの観測効果を達成することができます.
class Observer {
    constructor(value) {
        this.value = value 
        this.walk(this.value)
    }
    walk (value) {
        //     value   
        Object.keys(value).forEach((key) = > {
            defineReactive(value, key, value[key])
        })
    }
}
function defineReactive(obj, key ,val) {
    let childOb = observe(val)
    Obeject.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            console.log('')
            return val
        },
        set(newVal) {
            console.log('')
            val = newVal
            childOb = observe(val)
        }
    })
}
function observe (value) {
    if (typeof value === 'object' && !Array.isArray(value)) {
        value = new Observer(value)
    }
}

defineReactiveの役割はオブジェクトの属性に対して簡単なデータ観測を行い、値が取得または設定されるといくつかの行為をトリガする.1つのオブジェクトの属性がオブジェクトである可能性があるため、ここではobserve関数を追加して値を遍歴し、1つのオブジェクトの属性の属性が観測できるようにします.簡単に言えば、すべての属性を無視できるようにすることです.もちろん実際の状況では,配列の状況も考慮する必要があるが,いずれも大同小異である.このようにコードを作るのは少し醜いようで、私たちは属性を設定してsetをトリガしてconsoleが発生します.log()関数は、通知の変化をよりスマートに実現する方法はありませんか.ここではメッセージ購読器で実現する必要があります.これによりconsoleを観察する必要はありません.log()出力の値を見て行う場合,setメソッドに通知を加えるだけで,値が変化すると外値が変化したことを通知する.
Depの実装:
Depの役割は属性値の変化を収集し,setメソッドがトリガーされるとビューを更新することである.では、収集のために配列を用意しましょう.次はDepの実装です.
class Dep {
    constructor() {
        this.subs = []
    }
    addSub (sub) {
        this.subs.push(sub)
    }
    notify () {
        const subs = this.subs.slice()
        subs.forEach((sub) => {
            sub.update() //     
        })
    }
}

以上はDepの簡単な実装であり、addSubの役割はサブスクライバを増やすことであり、多くのサブスクライバがあるため、私たちは1つの配列でそれを格納する必要があります.notify()関数の役割はsetが発生したときに通知することです.update()という関数はwatcherで説明します.Depが実現したらset()関数を変更するべきではないでしょうか.次はdefineReactive()修正後のコードです.
function defineReactive(obj, key ,val) {
    let dep = new Dep() //      Dep   
    let childOb = observe(val)
    Obeject.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            return val
        },
        set(newVal) {
            val = newVal
            childOb = observe(val)
            dep.notify() //        ,     Dep
        }
    })
}

setがトリガーされるとdep.notify()が呼び出され、notifyの役割はサブスクライバの遍歴を更新することである.
Watcherのシンプルな実装:
watcherの役割は、状態が変化したときにビューを更新することです.
class Watcher {
    constructor (vm, cb, expOrFn) {
        this.vm = vm //      Vue   
        this.cb = cb
        //       expOrFn           
        //        ,        
        this.getter = expOrFn
        this.value = this.get()
    }
    get () {
        Dep.target = this 
        const vm = this.vm
        value = this.getter.call(this.vm, vm)
        Dep.target = null
        return value 
    }
    update () {
        this.run()
    }
    run () {
        const value = this.get()
        if (value !== this.value) {
            const oldValue = this.value
            this.value = value
            this.cb.call(this.vm, value, oldValue) 
        }
    }
}

Watcherの簡単な実装が完了しました.Dep()コンストラクション関数ではsub.update()という行のコードを使用していますが、update関数はWatcherの中の方法で、各subがWathcerの例であることを説明しています.問題はaddSub()という方法で、Watcherをsubsという配列にどのように加えて心を込めて記憶すべきかです.答えはdefineReactive()で修正します
function defineReactive(obj, key ,val) {
    let dep = new Dep() //      Dep   
    let childOb = observe(val)
    Obeject.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            if(Dep.target) {
                dep.addSub(Dep.target)
            }
            return val
        },
        set(newVal) {
            val = newVal
            childOb = observe(val)
            dep.notify() //        ,     Dep
        }
    })
}

これでDepにWatcherを入れることができたのではないでしょうか.vueソースコードの中ではこれよりずっと複雑で、いろいろなパラメータがあり、頭が大きいのを見ています.本文の目的は、内部原理を簡単に理解させることであり、より深く理解する必要がある場合はソースコードを読む必要があります.