专栏首页人生代码从头创建您自己的vue.js——第4部分(构建反应性)

从头创建您自己的vue.js——第4部分(构建反应性)

什么是状态反应?

状态反应是当应用程序(一组变量)的状态发生变化时,我们做某事(反应)。我们分两步来完成:

  1. 创建一个“反应依赖项”(当变量发生变化时,我们会得到通知)
  2. 创建“反应状态”(基本上是依赖变量的集合

函数监视更改

要做到这一点,我们首先需要一个在反应性依赖项发生变化时执行的函数。在Vue中,这被称为watchEffect;我们也会调用这个函数。

在我们的例子中,这个函数是这样的:

function watchEffect(fn) {
    activeEffect = fn
    fn()
    activeEffect = null
}

全局变量activeEffect是我们存储函数的临时变量,它被传递给watchEffect。这是必要的,因此我们可以在函数本身读取引用该函数的依赖项时访问该函数。

依赖类

我们可以将反应性依赖看作是一个变量,当它的值发生变化时通知它的订阅者。

它可以用一个初始值创建,因此我们需要一个构造函数

我们需要订阅一个函数来应对依赖项上的更改。我们将其称为depend()

当值改变时,我们需要一个通知订阅函数的依赖关系。我们将调用这个notify()

当值被读写时,我们需要做一些事情,所以我们需要一个getter和一个setter

所以我们的骨架是这样的:

class Dep {
    // Initialize the value of the reactive dependency
    constructor(value) {}

    // Subscribe a new function as observer to the dependency
    depend() {}

    // Notify subscribers of a value change
    notify() {}

    // Getter of the dependency. Executed when a part of the software reads the value of it.
    get value() {}

    // Setter of the dependency. Executed when the value changes
    set value(newValue) {}
}class Dep {
    // Initialize the value of the reactive dependency
    constructor(value) {}

    // Subscribe a new function as observer to the dependency
    depend() {}

    // Notify subscribers of a value change
    notify() {}

    // Getter of the dependency. Executed when a part of the software reads the value of it.
    get value() {}

    // Setter of the dependency. Executed when the value changes
    set value(newValue) {}
}

类有两个字段:value(依赖项的值)和subscribers(订阅函数集)。

我们一步一步地实现这个。

构造

constructor(value) {
    this._value = value // not `value` because we add getter/setter named value
    this.subscribers = new Set()
}

订阅者需要是一个集合,因此我们不会重复订阅相同的函数。

订阅一个函数

这里,我们需要订阅一个新函数作为依赖项的观察者。我们称之为依赖。

depend() {
    if (activeEffect) this.subscribers.add(activeEffect)
}

activeEffect是在watchEffect中设置的一个临时变量,本教程稍后将对此进行解释。

将依赖项更改通知订阅方

当值发生变化时,我们调用这个函数,以便在依赖项值发生变化时通知所有订阅者。

notify() {
    this.subscribers.forEach((subscriber) => subscriber())
}

我们这里所做的是执行每个订阅者。记住:这是一个订户是一个函数。

Getter

在依赖项的getter中,我们需要将activeEffect(当依赖项发生更改时将执行的函数)添加到订阅器列表中。换句话说,使用我们前面定义的depend()方法。

因此,我们返回当前值。

get value() {
    this.depend()
    return this._value
}

Setter

在依赖项的setter中,我们需要执行监视此依赖项的所有函数(订阅者)。换句话说,使用前面定义的notify()方法。

set value(newValue) {
    this._value = newValue
    this.notify()
}

试试

依赖项的实现已经完成。现在是时候试一试了。要做到这一点,我们需要做三件事:

定义一个依赖

添加要在依赖项更改时执行的函数

更改依赖项的值

// Create a reactive dependency with the value of 1
const count = new Dep(1)

// Add a "watcher". This logs every change of the dependency to the console.
watchEffect(() => {
    console.log('? value changed', count.value)
})

// Change value
setTimeout(() => {
    count.value++
}, 1000)
setTimeout(() => {
    count.value++
}, 2000)
setTimeout(() => {
    count.value++
}, 3000)

在控制台日志中,你应该可以看到这样的东西:

? value changed 1
? value changed 2
? value changed 3
? value changed 4

你可以找到完整的代码依赖?Github。

2. 构建反应状态

这只是谜团的第一部分,也是更好地理解接下来会发生什么的主要必要条件。

总结一下:我们有一个反应性依赖项和一个监视函数,它们让我们能够在变量(依赖项)发生变化时执行一个函数。这已经很酷了。但我们想更进一步,创建一个状态。

而不是像这样:

const count = Dep(1)
const name = Dep('Marc')
id.value = 2
name.value = 'Johnny'

我们想这样做:

const state = reactive({
    count: 1,
    name: 'Marc',
})
state.count = 2
state.name = 'Johnny'

为了实现这一点,我们需要对我们的代码做一些改变:

添加反应函数。这样就创建了“state”对象。

将getter和setter移到状态,而不是依赖项(因为这是发生变化的地方)

因此,依赖关系(Dep)将只起到这样的作用。只是依赖部分,不包含任何值。值存储在状态中。

reactive 函数

reactive()函数可以看作是状态的初始化。我们将带有初始值的对象传递给它,然后将其转换为依赖项。

对于每个对象属性,必须完成以下操作:

定义依赖关系(Dep)

定义者getter

定义赋值

function reactive(obj) {
    Object.keys(obj).forEach((key) => {
        const dep = new Dep()
        let value = obj[key]
        Object.defineProperty(obj, key, {
            get() {
                dep.depend()
                return value
            },
            set(newValue) {
                if (newValue !== value) {
                    value = newValue
                    dep.notify()
                }
            },
        })
    })
    return obj
}

依赖项上的更改

此外,我们需要从依赖项中移除getter和setter,因为我们现在是在反应状态下做的:

class Dep {
    subscribers = new Set()
    depend() {
        if (activeEffect) this.subscribers.add(activeEffect)
    }
    notify() {
        this.subscribers.forEach((sub) => sub())
    }
}

watchEffect功能保持不变。

试试代码

我们已经完成了将依赖变量转换为反应状态的工作。现在我们可以尝试代码:

const state = reactive({
    count: 1,
    name: 'Marc',
})

watchEffect(() => {
    console.log('? state changed', state.count, state.name)
})

setTimeout(() => {
    state.count++
    state.name = 'Johnny'
}, 1000)

setTimeout(() => {
    state.count++
}, 2000)
setTimeout(() => {
    state.count++
}, 3000)

打印出来:

? state changed 1 Marc
? state changed 2 Marc
? state changed 2 Johnny
? state changed 3 Johnny
? state changed 4 John

本文分享自微信公众号 - 志学Python(lijinwen1996329ken),作者:志学Python

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用Vue 3构建更好的高阶组件

    高阶组件(HOC)是使用模板声明性地向您的应用程序添加某些功能的组件。我相信即使引入了Composition API,它们仍将保持非常重要的关联。

    公众号---人生代码
  • 带你省市级联动操作

    公众号---人生代码
  • javascript入门到进阶 - js系列一:三种基本的数据结构

    做一件事首先有三个步骤:第一步:是什么,也就是 what 第二步:为什么,也就是 why 第三步:如何应用,也就是 how

    公众号---人生代码
  • lodash 是如何做类型检测的

    JS 的基本数据类型有 Number,String,Boolean,Symbol,Null,Undefined,六种数据类型。一种引用类型 object。

    Javanx
  • 高流量站点NGINX与PHP-fpm配置优化

    使用Nginx搭配PHP已有7年的这份经历让我们学会如何为高流量站点优化NGINX和PHP-fpm配置。 以下正是这方面的一些提示和建议: 1. 将TCP切换为...

    小小科
  • 排序算法-冒泡排序

    排序算法-冒泡排序 <?php /** * 冒泡排序 * * @param array $value 待排序数组 * * @return array...

    琯琯
  • 关于 python 中使用 lambda 表达式的问题

    --------------------------------  def doLambda(val):   print "value 2:", val ...

    田春峰-JCJC错别字检测
  • 大众点评App的短视频耗电量优化实战

    前言 美团点评测试团队负责App的质量保证工作,日常除了App的功能测试以外,还会重点关注App的性能测试。现在大家对手机越来越依赖,而上面各App的耗电量,直...

    美团技术团队
  • AppScan安全漏洞说明及解决方案

    解决方案:向所有会话cookie 添加HttpOnly属性 ,可以在过滤器中统一添加。

    java乐园
  • 什么,你算出的P-value看上去像齐天大圣变的庙?

    前几天,Nature上一篇comment再度引发关于p-value如何使用和解释的文章:Scientists rise up against statistic...

    生信宝典

扫码关注云+社区

领取腾讯云代金券