一、介绍
1. Vue3.0响应式特点
- Proxy对象实现属性监听
- 多层属性嵌套,只有在访问对应层级属性的时候才会处理对应层级的属性
- 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听数组索引和length属性
- 可以作为单独的模块使用
2. 核心函数
- reactive/ref/toRefs/computed
- effect
- track
- trigger 注:trigger/track/effct是低层的函数,一般不用,使用computed代替effect的使用
二、Proxy对象回顾
1. 在严格模式下,Proxy的函数必须返回布尔类型的值,否则会报TypeError
Uncaught TypeError: ‘set’ on proxy: trap returned falsish for property ‘foo’
'use strict' // 使用严格模式
// 问题1: set和deleteProperty中需要返回布尔类型的值
// 严格模式下,如果返回false的话,会出现TypeError的异常
const target = {
foo: 'xxx',
bar: 'yyy'
}
// Reflect.getPrototypeOf()
// Object.getPrototypeOf()
const proxy = new Proxy(target, {
get (target, key, receiver) {
// return target[key]
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
// target[key] = value
return Reflect.set(target, key, value, receiver) // 这里得写return
},
deleteProperty(target, key) {
// delete target[key]
return Reflect.deleteProperty(target, key) // 这里得写return
}
})
proxy.foo = 'zzz'
2. Proxy和Reflect中使用receiver
Proxy中receiver:指向的是Proxy或者继承Proxy的对象
Reflect中receiver:如果target对象设置了getter,getter中的this指向receiver
const obj = {
get foo () {
console.log(this)
return this.bar
}
}
const proxy = new Proxy(obj, {
get(target, key, receiver) {
if (key === 'bar') {
return 'value - bar'
}
return Reflect.get(target, key, receiver) // 执行this.bar的时候,this指向代理对象,也就是获取target.bar
}
})
console.log(proxy.foo) // value - bar
如果return Reflect.get(target, key, receiver)写成return Reflect.get(target, key)的话,则响应式属性foo里面的this还是指向原本的对象obj,this.bar就是undefined,而传入了receiver之后,响应式属性里的this就指向新的响应式对象proxy,this.bar返回value - bar。
三、reactive
- 接受一个参数,判断这个参数是否是对象
- 创建拦截器对象handler,设置get/set/deleteProperty
- 返回Proxy对象
自己创建一个js文件实现reactive
// 判断target是否是为对象
const isObject = (val) => {
if (val !== null && typeof val === 'object') {
return true
} else {
return false
}
}
// 如果result依然是个对象,继续递归调用reactive函数
const convert = (result) => {
// 如果result依然是个对象
if (isObject(result)) {
// 继续递归调用reactive函数
reactive(result)
} else {
return result
}
}
// reactive()函数
export function reactive(target) {
// 判断target是否是为对象,不是直接原样返回
if (!isObject(target)) return target
// target是对象,调用Proxy的的getter和setter方法,把对象变成响应式对象,返回
return new Proxy(target, {
get(target, prop, receiver) {
// 调用tarck方法去收集依赖
track(target, prop)
// Reflect.get的返回值: 如果属性key存在,则返回属性key对应的值value;否则返回 undefined
const result = Reflect.get(target, prop, receiver)
// 如果result依然是个对象,继续递归调用reactive函数
return convert(result)
},
set(target, prop, value, receiver) {
const oldValue = Reflect.get(target, prop, receiver)
let result = true
if (oldValue !== value) {
// Reflect.set的返回值: 如果设置属性成功,则返回 true;否则返回false
// return Reflect.set(target, prop, value, receiver)
result = Reflect.set(target, prop, value, receiver)
//调用trigger函数触发更新操作
trigger(target, prop)
}
return result
},
deleteProperty(target, propName) {
//判断对象内是否具有指定的属性
if (Object.keys(target).indexOf(propName) !== -1) {
const result = Reflect.deleteProperty(target, propName)
if (result) {
//调用trigger函数触发更新操作
trigger(target, propName)
}
return result
}
},
})
}
自己创建一个html文件使用自己创建的reactive
<body>
<script type="module">
import {reactive,effect,ref,toRefs,computed} from './js/myIndex.js''
const obj = reactive({
name: 'zs',
age: 18
})
obj.name = 'lisi'
delete obj.age
console.log(obj)
</script>
</body>
四、收集依赖 effect、track
自己创建一个js文件实现effect、track
// effect函数 是用户来调用的
let activeEffect = null
export function effect(callback) {
activeEffect = callback // 用户调用effect函数的时候,传入的回调函数
// 执行回调函数,通过函数内的数据得知,是要去获取数据的值,即要去执行Proxy中的get方法
// 然后在get方法中,调用去trak方法去收集依赖
// trak方法中把activeEffect函数(即activeEffect = callback 用户传入的回调函数)保存起来了
callback()
activeEffect = null // 防止内存溢出,执行完callback后,把activeEffect值清空
}
// track函数 :用来收集依赖
// target: 目标对象 propName:目标对象里的属性名
let targetMap = new WeakMap()
export function track(target, propName) {
if (!activeEffect) return
// 从targetMap中找到对应的target对象
let depsMap = targetMap.get(target)
if (!depsMap) {
// 向targetMap中新增一个target对象,值为Map
depsMap = new Map()
targetMap.set(target, depsMap)
}
console.log('targetMap:', targetMap)
// 如果depsMap中有值,根据属性名字,找到dep
let dep = depsMap.get(propName)
if (!dep) {
dep = new Set()
depsMap.set(propName, dep)
}
console.log('depsMap:', depsMap)
// 向dep内添加effect方法的回调函数
dep.add(activeEffect)
console.log('dep:', dep)
}
自己创建一个html文件使用自己创建的effect
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script type="module">
import {reactive,effect,ref,toRefs,computed} from './js/myIndex.js'
// 测试effect函数
const obj = reactive({
name:'iphone',
price:5000,
count:3,
})
let total = 0
effect(()=>{
total = obj.price * obj.count
})
console.log(total)
obj.price = 4000
console.log(total)
obj.count = 1
console.log(total)
</script>
五、trigger
自己创建一个js文件实现trigger
// effect函数 是用户来调用的
let activeEffect = null
export function effect(callback) {
activeEffect = callback // 用户调用effect函数的时候,传入的回调函数
// 执行回调函数,通过函数内的数据得知,是要去获取数据的值,即要去执行Proxy中的get方法
// 然后在get方法中,调用去trak方法去收集依赖
// trak方法中把activeEffect函数(即activeEffect = callback 用户传入的回调函数)保存起来了
callback()
activeEffect = null // 防止内存溢出,执行完callback后,把activeEffect值清空
}
// track函数 :用来收集依赖
// target: 目标对象 propName:目标对象里的属性名
let targetMap = new WeakMap()
export function track(target, propName) {
if (!activeEffect) return
// 从targetMap中找到对应的target对象
let depsMap = targetMap.get(target)
if (!depsMap) {
// 向targetMap中新增一个target对象,值为Map
depsMap = new Map()
targetMap.set(target, depsMap)
// targetMap.set(target, (depsMap = new Map()))
}
console.log('targetMap:', targetMap)
// 如果depsMap中有值,根据属性名字,找到effect方法的回调函数
let dep = depsMap.get(propName)
if (!dep) {
dep = new Set()
depsMap.set(propName, dep)
// depsMap.set(propName, (dep = new Set()))
}
console.log('depsMap:', depsMap)
// 向dep内添加effect方法的回调函数
dep.add(activeEffect)
console.log('dep:', dep)
}
// trigger函数:更新响应式数据
export function trigger(target, propName) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(propName)
if (dep) {
dep.forEach((effect) => {
effect()
})
}
}
自己创建一个html文件使用自己创建的trigger
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script type="module">
import {reactive,effect,ref,toRefs,computed} from './js/myIndex.js'
// 测试effect函数和trigger函数
// trigger不需要导入,响应式数据更改后,自动在reactive函数中的set方法内执行
const obj = reactive({
name:'iphone',
price:5000,
count:3,
})
let total = 0
effect(()=>{
total = obj.price * obj.count
})
console.log(total)
obj.price = 4000 // trigger不需要导入,响应式数据更改后,自动在reactive函数中的set方法内执行
console.log(total)
obj.count = 1 // trigger不需要导入,响应式数据更改后,自动在reactive函数中的set方法内执行
console.log(total)
</script>
六、ref
reactive 和 ref 的对比
- ref可以把基本数据类型数据转换成响应式对象
- ref返回的对象,重新赋值成对象也是响应式的
- reactive返回的对象,重新赋值丢失响应式
- reactive返回的对象不可解构
自己创建一个js文件实现ref
// ref函数
export function ref(raw) {
// 判断 raw 是否是 ref 创建的对象,如果是的话,直接返回
if (isObject(raw) && raw.__v_isRef) return
// 调用convert()函数把传入的数据,变成响应式对象
let value = convert(raw)
console.log(value)
// ref 函数自身,最终要返回一个对象
const r = {
__v_isRef: true, // 对象是ref创建的对象的一个标识
get value() {
// 调用track函数收集依赖
track(r, 'value')
return value
},
set value(newValue) {
if (newValue !== value) {
raw = newValue
value = convert(raw)
// 调用trigger函数触发更新
trigger(r, 'value')
}
},
}
return r
}
自己创建一个html文件使用自己创建的ref
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script type="module">
import {reactive,effect,ref,toRefs,computed} from './js/myIndex.js'
// 测试ref函数
const price = ref(5000)
const count = ref(3)
let total = 0
effect(()=>{
total = price.value * count.value
})
console.log(total)
price.value = 4000
console.log(total)
count.value = 1
console.log(total)
</script>
七、toRefs
自己创建一个js文件实现toRefs
// roRefs函数:把reactive函数创建的相应是对象,变成ref类型的响应式对象,同时可以解构
// proxy 是 reactive函数创建后,生成的代理对象
export function toRefs(proxy) {
const ret = proxy instanceof Array ? new Array(proxy.length) : {}
for (const key in proxy) {
ret[key] = toProxyRef(proxy, key)
}
return ret
}
function toProxyRef(proxy, key) {
const r = {
__v_isRef: true,
get value() {
return proxy[key]
},
set value(newValue) {
proxy[key] = newValue
},
}
return r
}
自己创建一个html文件使用自己创建的toRefs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script type="module">
import {reactive,effect,ref,toRefs,computed} from './js/myIndex.js'
// 测试toRefs函数
const reactiveFn = ()=>{
const obj = reactive({
name:'iphone',
price:5000,
count:3,
})
return toRefs(obj)
}
const {price,count} = reactiveFn()
let total = 0
effect(()=>{
total = price.value * count.value
})
console.log(total)
price.value = 4000
console.log(total)
count.value = 1
console.log(total)
console.log(total,'00000000000000000000000')
</script>
八、computed
自己创建一个js文件实现computed
// computed函数
export function computed(getterCallBack) {
const result = ref() // 创建一个ref类型的响应式对象
// 调用 effect函数 要求传入一个函数作为参数
// getterCallBack 是computed函数调用的时候,用户传递的函数
// 把getterCallBack函数的返回值,赋值给result
effect(() => (result.value = getterCallBack()))
return result
}
自己创建一个html文件使用自己创建的computed
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script type="module">
import {reactive,effect,ref,toRefs,computed} from './js/myIndex.js'
// 测试computed函数
const reactiveFn = ()=>{
const obj = reactive({
name:'iphone',
price:5000,
count:3,
})
return toRefs(obj)
}
const {price,count} = reactiveFn()
const total = computed(()=>{
return price.value * count.value
})
console.log(total.value)
price.value = 4000
console.log(total.value)
count.value = 1
console.log(total.value)
console.log(total,'computed')
</script>