Принцип отзывчивости Vue и простая реализация

JavaScript

В принципе Vue наиболее важной частью является то, как реализовать наблюдение за данными, сбор зависимостей и обновление представлений. Эта статья о простой реализации Observer, Dep, Watcher. pub(publish) представляет издателя, sub(subscribe) представляет подписчика, а cb(callback) представляет функцию обратного вызова Если вы считаете, что эта статья полезна для вас, пожалуйста, помогите мнеstar

Реализация наблюдателя

Функция наблюдателя состоит в том, чтобы просто определить свойства объекта объекта с помощью Object.defineProperty(), чтобы при получении или изменении свойств объекта можно было инициировать получение и установку для достижения эффекта наблюдения за данными.

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 заключается в простом наблюдении за данными о свойствах объекта. После того, как значение будет получено или установлено, будут запущены некоторые действия. Поскольку свойства объекта могут по-прежнему быть объектами, здесь мы добавляем функцию наблюдения для обхода значения и пусть свойства объекта Атрибуты все еще можно наблюдать, что просто означает, что все атрибуты можно игнорировать. Конечно, на практике нужно рассматривать и случай массивов, но они все одинаковые. Этот код кажется немного уродливым. Когда мы устанавливаем свойство для запуска набора, вызывается функция console.log(). Есть ли более разумный способ уведомить об изменении? Здесь нам нужно использовать подписчик сообщений для его реализации. Таким образом, нам не нужно наблюдать за выходным значением console.log(), чтобы увидеть ситуацию. Нам нужно только добавить уведомление в метод set. значение изменяется, уведомляет, что внешнее значение изменилось

Реализация Деп:

Роль 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 заключается в увеличении числа подписчиков. Поскольку подписчиков много, нам нужно хранить их в массиве. Роль функции notify() заключается в уведомлении о появлении набора, обновлении () Эта функция будет рассмотрена в наблюдателе позже. Следует ли после реализации 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
        }
    })
}

Как только набор запускается, вызывается dep.notify(), и функция уведомления заключается в обновлении обхода подписчика.

Простая реализация 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(), а функция обновления представляет собой метод Watcher, указывающий, что каждая подпрограмма является экземпляром Watcher. Проблема заключается в следующем. что мы Как я должен добавить Watcher в массив subs и сохранить его с помощью метода addSub() Ответ заключается в том, чтобы изменить его в 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
        }
    })
}

Позволяет ли это добавить Watcher в Dep?Исходный код Vue намного сложнее этого, с различными параметрами, и он выглядит большим. Цель этой статьи — помочь вам понять внутренние принципы через упрощение.Если вам нужно более глубокое понимание, вам нужно прочитать исходный код.