1)Vue.js 响应式回顾
- Proxy 对象实现属性监听
- 多层属性嵌套,在访问属性过程中处理下一级属性
- 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听数组索引和 length 属性
- 可以作为单独的模块使用
2)Proxy
'use strict'
// 问题1: set 和 deleteProperty 中需要返回布尔类型的值
// 在严格模式下,如果返回 false 的话会出现 Type Error 的异常
const target = {
foo: 'xxx',
bar: 'yyy'
}
// Reflect.getPrototypeOf()
// Object.getPrototypeOf()
const proxy = new Proxy(target, {
// 访问
// receiver 表示当前的 proxy 对象或者继承的 proxy对象
get (target, key, receiver) {
// return target[key]
// Reflect(用来操作对象的成员) 反射 es6中新增的成员 代码运行期间用来获取或者设置对象的成员
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
return Reflect.get(target, key, receiver)
},
// 赋值
set (target, key, value, receiver) {
// target[key] = value
return Reflect.set(target, key, value, receiver)
},
// 删除
deleteProperty (target, key) {
// delete target[key]
return Reflect.deleteProperty(target, key)
}
})
proxy.foo = 'zzz'
// delete proxy.foo
// 问题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)
}
})
console.log(proxy.foo)
3)reactive
- 接收一个参数,判断这参数是否是对象
- 创建拦截器对象 handler, 设置 get/set/deleteProperty
- 返回 Proxy 对象
4)收集依赖
5)effect / track / trigger
- effect 跟踪属性变化并调用回调函数
- tarck 收集依赖
- trigger 触发更新
6)ref
把一个基本数据类型数据,转成响应式对象,后续通过 .value 使用
7)reactive vs ref
- ref 可以把基本数据类型数据,转成响应式对象
- ref 返回的对象,重新赋值成对象也是响应式的
- reactive 返回的对象,重新赋值丢失响应式
- reactive 返回的对象不可以解构
8)toRefs
将reactive()创建出来的响应式对象,转换为普通对象,只不过这个对象上的每个属性节点,都是ref()类型的响应式数据
9)实现代码
// index.js
// 判断是否是对象
const isObject = val => val!==null && typeof val ==='object'
// 判断是否是对象,如果是转换成响应式对象,不是则返回本身
const convert = target => isObject(target) ? reactive(target) : target
// 判断某个对象本身是否具有指定的属性
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)
export function reactive (target) {
if(!isObject(target)) return target
//
const handler = {
get (target, key, receiver) {
// 收集依赖
// console.log('get', target)
track(target, key)
const result = Reflect.get(target, key, receiver)
return convert(result)
},
set (target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
let result = true
if (oldValue !== value) {
result = Reflect.set(target, key, value, receiver)
// 触发更新
// console.log('set', key, value)
trigger(target, key)
}
return result
},
deleteProperty (target, key) {
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
//触发更新
// console.log('delete', key)
trigger(target, key)
}
return result
}
}
return new Proxy(target, handler)
}
let activeEffect = null
export function effect (callback) {
activeEffect = callback
callback() // 访问响应式对象属性,去收集依赖
activeEffect = null
}
let targetMap = new WeakMap()
// 收集依赖
export function track (target, key) {
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)
}
// 触发更新
export function trigger (target, key) {
// console.log(" ~ file: index.js ~ line 79 ~ trigger ~ target", target)
// console.log(" ~ file: index.js ~ line 87 ~ trigger ~ targetMap", targetMap)
const depsMap = targetMap.get(target)
// console.log(" ~ file: index.js ~ line 80 ~ trigger ~ depsMap", depsMap)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => {
effect()
})
}
}
export function ref (raw) {
// 判断 raw 是否是 ref 创建的对象,如果是的话直接返回
if (isObject(raw) && raw.__v_isRef) {
return
}
// raw 如果是对象,调用 reactive 转换成响应式对象
let value = convert(raw)
const r = {
__v_isRef: true,
get value () {
track(r, 'value')
return value
},
set value (newValue) {
if (newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
return r
}
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
}
export function computed (getter) {
const result = ref()
// effect(() => (result.value = getter()))
effect(() => result.value = getter())
return result
}
10)测试代码
index.html
<!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>
<script type="module">
import { reactive } from './index.js'
const obj = reactive({
name: 'xcc',
age: 18
})
obj.name = 'nopear'
delete obj.age
console.log(obj)
</script>
</body>
</html>
effect-demo.html
<!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>
<script type="module">
// import { reactive, effect } from './node_modules/@vue/reactivity/dist/reactivity.esm-browser.js'
import { reactive, effect } from './index.js'
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = 0
effect(() => {
// console.log(111)
total = product.price * product.count
})
console.log(total)
product.price = 4000
console.log(total)
product.count = 1
console.log(total)
</script>
</body>
</html>
ref.html
<!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>
<script type="module">
import { reactive, effect, ref } from './index.js'
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>
</body>
</html>
toRefs.html
<!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>
<script type="module">
import { reactive, effect, toRefs } from './index.js'
function useProduct () {
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
return toRefs(product)
}
const { price, count } = useProduct()
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>
</body>
</html>
computed.html
<!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>
<script type="module">
import { reactive, effect, computed } from './index.js'
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = computed(() => {
return product.price * product.count
})
console.log(total.value)
product.price = 4000
console.log(total.value)
product.count = 1
console.log(total.value)
</script>
</body>
</html>