当前位置: 首页 > article >正文

​Vue3响应式原理

目录

手动收集依赖+通知更新

effect():更改数据后执行,更新依赖该数据的数据(依赖)

track()收集依赖的effect()放进dep(set去重)

更新时触发trigger函数通知dep里所有effect()执行

当依赖的为object类型:WeakMap存obj,Map存obj.props

proxy:自动收集依赖+通知更新

new Proxy(target, handler)

Reflect

Reflect.get(target, propertyKey[, receiver])函数调用方式从对象中读值

reactive(被依赖的数据){return proxy}

vue3新增全局变量activeEffect存储当前执行的effect

track不必再判断obj和obj.key,直接dep存储当前activeEffect

应用

实现ref响应式

用ref实现computed

与Vue2区别

使用

初始化、监听:整个对象级别上进行拦截,无需遍历每个属性

性能:监听整个操作(不用遍历),不仅是读写

功能:可监听属性的增删

收集依赖

类型推导:TypeScript


手动收集依赖+通知更新

effect():更改数据后执行,更新依赖该数据的数据(依赖)

let name = '林三心', age = 22, money = 20
let myself = '', ohtherMyself = ''
const effect1 = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => ohtherMyself = `${age}岁的${name}居然有${money}元`

effect1() // 先执行一次
effect2() // 先执行一次
console.log(myself) // 林三心今年22岁,存款20元
console.log(ohtherMyself) // 22岁的林三心居然有20元
money = 300

effect1() // 再执行一次
effect2() // 再执行一次

console.log(myself) // 林三心今年22岁,存款300元
console.log(ohtherMyself) // 22岁的林三心居然有300元

track()收集依赖的effect()放进dep(set去重)

更新时触发trigger函数通知dep里所有effect()执行

let name = '林三心', age = 22, money = 20
let myself = '', ohtherMyself = ''
const effect1 = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => ohtherMyself = `${age}岁的${name}居然有${money}元`

const dep = new Set()
function track () {
    dep.add(effect1)
    dep.add(effect2)
}
function trigger() {
    dep.forEach(effect => effect())
}
track() //收集依赖
effect1() // 先执行一次
effect2() // 先执行一次
console.log(myself) // 林三心今年22岁,存款20元
console.log(ohtherMyself) // 22岁的林三心居然有20元
money = 300

trigger() // 通知变量myself和otherMyself进行更新

console.log(myself) // 林三心今年22岁,存款300元
console.log(ohtherMyself) // 22岁的林三心居然有300元

当依赖的为object类型:WeakMap存obj,Map存obj.props

const targetMap = new WeakMap()
function track(target, key) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        targetMap.set(target, depsMap = new Map())
    }

    let dep = depsMap.get(key)
    if (!dep) {
        depsMap.set(key, dep = new Set())
    }

...
}
function trigger(target, key) {
    let depsMap = targetMap.get(target)
    if (depsMap) {
        const dep = depsMap.get(key)
        if (dep) {
            dep.forEach(effect => effect())
        }
    }
}

proxy:自动收集依赖+通知更新

new Proxy(target, handler)

target

包装target (对象/数组/函数甚/proxy对象)

handler

被代理对象上的自定义行为(定义一组处理函数(例如get、set)的对象)

target:被代理者
prop:被代理者的属性
receiver:代理者 ,Proxy 或者继承 Proxy 的对象

const person = { name: '林三心', age: 22 }

const proxyPerson = new Proxy(person, {
    get(target, key, receiver) {
        return target[key]
    },
    set(target, key, value, receiver) {
        target[key] = value
    }
})

console.log(proxyPerson.name) // 林三心

proxyPerson.name = 'sunshine_lin'

console.log(proxyPerson.name) // sunshine_lin

Reflect

Proxy和Reflect的方法都是一一对应的,在Proxy里使用Reflect会提高语义化

  • Proxy的get对应Reflect.get
  • Proxy的set对应Reflect.set

属性访问方法

A.属性访问器(访问):obj.key,obj[key]

B.函数调用:mp.get(key)

Reflect.get(target, propertyKey[, receiver])函数调用方式从对象中读值

receiver

如果target对象中指定了getterreceiver则为getter调用时的this

该方法会拦截目标对象的以下操作:

  • 访问属性:proxy[foo] 和 proxy.bar
  • 访问原型链上的属性:Object.create(proxy)[foo]

  • Reflect.get()
const person = { name: '林三心', age: 22 }

const proxyPerson = new Proxy(person, {
    get(target, key, receiver) {
        return Reflect.get(receiver, key) // 相当于 receiver[key]
    },
    set(target, key, value, receiver) {
        Reflect.set(receiver, key, value) // 相当于 receiver[key] = value
    }
})

console.log(proxyPerson.name)

proxyPerson.name = 'sunshine_lin' 
// 会直接报错,栈内存溢出 Maximum call stack size exceeded

reactive(被依赖的数据){return proxy}

function reactive(target) {
    const handler = {
        get(target, key, receiver) {
            track(receiver, key) // 访问时收集依赖
            return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
            Reflect.set(target, key, value, receiver)
            trigger(receiver, key) // 设值时自动通知更新
        }
    }

    return new Proxy(target, handler)
}

const person = reactive({ name: '林三心', age: 22 }) // 传入reactive
const animal = reactive({ type: 'dog', height: 50 }) // 传入reactive

...

vue3新增全局变量activeEffect存储当前执行的effect

track不必再判断obj和obj.key,直接dep存储当前activeEffect

let activeEffect = null
function effect(fn) {
    activeEffect = fn
    activeEffect()
    activeEffect = null // 执行后立马变成null
}
function track(target, key) {
    // 如果此时activeEffect为null则不执行下面
    // 这里判断是为了避免例如console.log(person.name)而触发track
    if (!activeEffect) return
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        targetMap.set(target, depsMap = new Map())
    }

    let dep = depsMap.get(key)
    if (!dep) {
        depsMap.set(key, dep = new Set())
    }
    dep.add(activeEffect) // 把此时的activeEffect添加进去
}

// 每个effect函数改成这么执行
effect(effectNameStr1)
effect(effectNameStr2)
effect(effectAgeStr1)
effect(effectAgeStr2)
effect(effectTypeStr1)
effect(effectTypeStr2)
effect(effectHeightStr1)
effect(effectHeightStr2)

应用

实现ref响应式

function ref (initValue) {
    return reactive({
        value: initValue
    })
}

let num = ref(5)//num就会成为一个响应式的数据
console.log(num.value) // 5

用ref实现computed

function computed(fn) {
    const result = ref()
    effect(() => result.value = fn()) // 执行computed传入函数
    return result
}


let num1 = ref(5)
let num2 = ref(8)
let sum1 = computed(() => num1.value * num2.value)
let sum2 = computed(() => sum1.value * 10)

console.log(sum1.value) // 40
console.log(sum2.value) // 400

num1.value = 10

console.log(sum1.value) // 80
console.log(sum2.value) // 800

num2.value = 16

console.log(sum1.value) // 160
console.log(sum2.value) // 1600

与Vue2区别

使用

Vue2 中只要写在组件中 data 函数返回的对象里的属性 自动就有响应式

Vue3 则是通过 ref 定义普通类型响应式和 reactive 定义复杂类型响应式数据

<script setup>
  import { ref, reactive, toRefs } from "vue"
  const name = ref('沐华')
  const obj = reactive({ name: '沐华' })
  const data = { ...toRefs(obj) }
</script>

通过 toRefs 可以把响应式对象转为普通对象
因为使用 reactive 定义的响应式对象在进行解构(展开)或者销毁的时候,响应式就会失效了,因为 reactive 实例下有很多属性解构就丢失了,所以在需要解构且保持响应式的时候就可以用 toRefs

初始化、监听:整个对象级别上进行拦截,无需遍历每个属性

性能:监听整个操作(不用遍历),不仅是读写

Proxy可以监听对象的整个操作,而不仅仅是属性的读写操作,这使得数据变化的检测更加高效。Vue2中,需要递归地遍历对象的所有属性,而Proxy可以一次性监听整个对象的操作。

功能:可监听属性的增删

Proxy可以监听新增属性和删除属性的操作,而Object.defineProperty只能监听已有属性的读写操作

收集依赖

Vue2 中是通过 ObserverDepWatcher 这三个类来实现依赖收集

Vue3 中是通过 track 收集依赖,通过 trigger 触发更新,本质上就是用 WeakMap,Map,Set 来实现

类型推导:TypeScript

由于Proxy是基于原生的Proxy对象实现的,所以可以更好地支持TypeScript等静态类型检查工具,提供更准确的类型推导和代码提示。

​Vue2响应式原理

林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析 - 掘金


http://www.kler.cn/news/107921.html

相关文章:

  • git clone失败
  • 《HelloGitHub》第 91 期
  • Flutter笔记:完全基于Flutter绘图技术绘制一个精美的Dash图标(中)
  • FFmpeg5.1.3编译动态库踩坑之旅(基于Linux虚拟机)
  • Ps:对象选择工具
  • 【torch高级】一种新型的概率学语言pyro(01/2)
  • PHP聊天系统源码 在线聊天系统网站源码 后台自适应PC与移动端
  • 2 第一个Go程序
  • 【Git推送本地项目到远程仓库】
  • TSINGSEE青犀省级高速公路视频上云联网方案:全面实现联网化、共享化、智能化
  • 【爬虫】python打包可执行程序(ui界面制作完成后)
  • 服务器感染了.secret勒索病毒,如何确保数据文件完整恢复?
  • python下拉框选择测试
  • 论文阅读——BERT
  • AI新能量!FortiGate NGFW面向数据中心全面集成FortiGuard AI 安全服务
  • Flutter框架实现登录注册功能,不连接数据库
  • ETCD备份与恢复
  • Tomcat的日志接收文件catalina.out nohup.out说明
  • C++编译与运行:其一、静态类型和动态类型
  • Vue--》简易资金管理系统后台项目实战(前端)
  • mac版本 Adobe总是弹窗提示验证问题如何解决
  • Go学习第十三章——Gin入门与路由
  • shell_52.Linux测试与其他网络主机的连通性脚本
  • x210项目重新回顾之十七升级到linux4.19.114 +buildroot2018再讨论
  • 常用adb 命令
  • 【mediasoup-sfu-cpp】4: SfuDemo:join并发布视频创建RTCTransport流程分析
  • [ubuntu系统下的文本编辑器nano,vim,gedit,文件使用,以及版本更新问题]
  • [100天算法】-尽量减少恶意软件的传播(day 45)
  • 搜索与图论:匈牙利算法
  • Vue3:将表格数据下载为excel文件