响应式原理(Vue3、Vue2)
1.Vue3
副作用函数(onMounted、watchEffect)
帮助管理组件中的副作用逻辑,并自动追踪其依赖关系,以确保在数据变化时能够自动触发副作用函数的更新。
会自动追踪被其内部函数引用的响应式数据。当这些数据发生变化时,Vue 3 会自动重新运行副作用函数,确保副作用与数据的状态保持同步。
effect就是组件的setup,我们定义ref,操作数据都是在里面实现的,所以具有了响应式的效果:在初始化进行依赖收集,在数据变化时重新触发effect执行
let activeEffect // 副作用函数 export const effect = (fn) => { const _effect = function () { activeEffect = _effect fn() } _effect() }
收集依赖
通过proxy里的调用收集依赖
// 收集依赖 const targetMap = new WeakMap() export const track = (target, key) => { // WeakMap // key为对象, 即target // value 为一个new Map() // { // key:{ // name: "名字", // age: 18 // }, // value: { // name: new Map(), // age: new Map() // } // } let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } // 现在添加new Map() // key为属性名称, 即key // value为一个new Set() let deps = depsMap.get(key) if (!deps) { deps = new Set() depsMap.set(key, deps) } // 现在添加Set() // 给Set添加副作用函数 deps.add(activeEffect) }
更新依赖
// 更新依赖 export const trigger = (target, key) => { // 根据组件的响应式组件对象,拿到对应的map const depsMap = targetMap.get(target) // 通过具体key, 拿到对应副作用函数 const deps = depsMap.get(key) // 执行 deps.forEach((effect) => effect()) }
完整代码(effect.js)
let activeEffect // 副作用函数 export const effect = (fn) => { const _effect = function () { activeEffect = _effect fn() } _effect() } // 收集依赖 const targetMap = new WeakMap() export const track = (target, key) => { // WeakMap // key为对象, 即target // value 为一个new Map() // { // key:{ // name: "名字", // age: 18 // }, // value: { // name: new Map(), // age: new Map() // } // } let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } // 现在添加new Map() // key为属性名称, 即key // value为一个new Set() let deps = depsMap.get(key) if (!deps) { deps = new Set() depsMap.set(key, deps) } // 现在添加Set() // 给Set添加副作用函数 deps.add(activeEffect) } // 更新依赖 export const trigger = (target, key) => { // 根据组件的响应式组件对象,拿到对应的map const depsMap = targetMap.get(target) // 通过具体key, 拿到对应副作用函数 const deps = depsMap.get(key) // 执行 deps.forEach((effect) => effect()) }
reactive实现(reactive.js)
import { track, trigger } from './effect.js' const isObject = (target) => target != null && typeof target == 'object' export const reactive = (target) => { return new Proxy(target, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver) // 收集依赖 track(target, key) // 递归对象 if (isObject(res)) { return reactive(res) } return res }, set(target, key, value, receiver) { const res = Reflect.set(target, key, value, receiver) // 更新依赖 trigger(target, key) return res }, }) }
调用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> </div> <script type="module"> import { reactive } from './reactive.js' import { effect } from './effect.js' const user = reactive({ name: "名字", age: 18, foo: { bar: { sss: 123 } } }) effect(() => { document.querySelector('#app').innerText = `${user.name} - ${user.age}-${user.foo.bar.sss}` }) setTimeout(() => { user.name = '名字666' setTimeout(() => { user.age = '23' setTimeout(() => { user.foo.bar.sss = 66666666 }, 3000) }, 2000) }, 1000) </script> </body> </html>
2.Vue2
数据驱动视图
1.使用Object.defineProperty数据劫持所有的属性(递归);
2.通过消息订阅器Dep,在收集依赖(push),更新依赖进行统一的操作(通知订阅者Watcher)
视图驱动数据
1.解析根节点dom元素(绑定的),创建一个fragment(不影响原dom);
2.对dom的节点进行遍历,然后塞进fragment里
3.将fragment里的childNodes进行递归遍历;判断是元素节点还是{{}}写法的文本节点;
4.{{}}写法的文本节点,绑定更新函数的订阅器Watcher处理按照之前的数据驱动视图
5.元素节点判断是v-model还是v-on:其他的或者@开始的等等;
6.若是v-model的将数据绑定更新函数的订阅器Watcher,然后给这个node绑定监听事件,若更新则会执行数据驱动视图
7.@的事件则是回调那个方法即可