Vue.set()
vue2.0 中不能间接监听对象中新增属性的变动,如果须要监听,须要通过 Vue.set(target, propertyName/index, value)办法增加
set 函数通过 Object.defineProperty
将传入的对象上的属性变为响应式属性,简易版实现如下:
const set = (target, prop, initValue) => {
let value = initValue
let dep = new Dep()
return Object.defineProperty(target, prop, {get() {dep.depend()
return value
},
set(newValue) {
value = newValue
dep.notify()}
})
}
这段代码中的逻辑与 ref 函数中的逻辑反复,将代码提取放到 createReactive 函数中。
数组响应式
Vue 源码中对于 push pop shift unshift splice sort reverse
这些办法进行了解决,使得通过这些办法操作数组时能感知到数据的变动。
解决数组原型上的 push 办法
- 通过 set 生成一个响应式数组,在执行 set 函数时,曾经增加了依赖
- 革新数组原型上的 push 办法。首先将原型上的 push 办法存储起来,再从新定义 Array.prototype.push。
- 在新办法中首先执行原本的 push 操作,而后须要调用 notify 办法,触发依赖的执行。notify 办法挂载在 createReactive 函数内的 dep 实例上,这里的 this 即 createReactive 函数中的 target 对象,所以能够革新 createReactive 函数,将 dep 实例挂载到 target 的_dep 属性上。这样就能够拿到并触发 notify 了。
let createReactive = (target, prop, value) => {// let dep = new Dep()
target._dep = new Dep()
if (Array.isArray(target)) {target.__proto__ = arrayMethods}
return Object.defineProperty(target, prop, {get() {target._dep.depend()
return value
},
set(newValue) {
value = newValue
target._dep.notify()}
})
}
let push = Array.prototype.push
let arrayMethods = Object.create(Array.prototype)
arrayMethods.push = function (...args) {push.apply(this, [...args])
// 这里须要调用 notify 办法
// notify 办法挂载在 createReactive 函数内的 dep 实例上,批改为挂载到 target 上
// 这里通过 this 就能够拿到 notify 办法
this._dep && this._dep.notify()}
残缺带示例代码:
<!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>
<button id="add">add</button>
<div id="app"></div>
<hr>
<button id="addArr">addArr</button>
<div id="appArr"></div>
</body>
<script>
let active
let effect = (fn, options = {}) => {
// 为什么要减少一个_effect 函数
// 因为须要给_effect 减少属性
// 也能够间接给 fn 减少,然而因为援用类型的起因,会对 fn 函数造成净化
let _effect = (...args) => {
try {
active = _effect
return fn(...args)
} finally {active = null}
}
_effect.options = options
_effect.deps = [] // effect 和 dep 的关系 -1
return _effect
}
let cleanUpEffect = (effect) => {
// 革除依赖
// 须要反向查找 effect 被哪些 dep 依赖了
// 在 effect 上增加[] 建设双向索引
const {deps} = effect
console.log(deps)
console.log(effect)
if (deps.length) {for (let i = 0; i < deps.length; i++) {deps[i].delete(effect)
}
}
}
let watchEffect = (cb) => {
/* active = cb
active()
active = null */
let runner = effect(cb)
runner()
return () => {cleanUpEffect(runner)
}
}
let nextTick = (cb) => Promise.resolve().then(cb)
// 队列
let queue = []
// 增加队列
let queueJob = (job) => {if (!queue.includes(job)) {queue.push(job)
// 增加之后,将执行放到异步工作中
nextTick(flushJob)
}
}
// 执行队列
let flushJob = () => {while (queue.length > 0) {let job = queue.shift()
job && job()}
}
let Dep = class {constructor() {
// 寄存收集的 active
this.deps = new Set()}
// 依赖收集
depend() {if (active) {this.deps.add(active)
active.deps.push(this.deps) // effect 和 dep 的关系 -2
}
}
// 触发
notify() {this.deps.forEach(dep => queueJob(dep))
this.deps.forEach(dep => {dep.options && dep.options.schedular && dep.options.schedular()
})
}
}
let createReactive = (target, prop, value) => {// let dep = new Dep()
target._dep = new Dep()
if (Array.isArray(target)) {target.__proto__ = arrayMethods}
return Object.defineProperty(target, prop, {get() {target._dep.depend()
return value
},
set(newValue) {
value = newValue
target._dep.notify()}
})
}
let ref = (initValue) => createReactive({}, 'value', initValue)
const set = (target, prop, initValue) => createReactive(target, prop, initValue)
let computed = (fn) => {
let value
let dirty = true // 为 true 表明依赖的变量产生了变动,此时须要从新计算
let runner = effect(fn, {schedular() {if (!dirty) {dirty = true}
}
})
return {get value() {if (dirty) {
// 何时将 dirty 重置为 true,当执行 fn 后
// 因而须要通过配置回调函数,在执行 fn 后将 dirty 重置为 true
// value = fn()
value = runner()
dirty = false
}
return value
}
}
}
let watch = (source, cb, options = {}) => {const { immediate} = options
const getter = () => {return source()
}
// 将函数增加到 count 的依赖下来,当 count 变动时
let oldValue
const runner = effect(getter, {schedular: () => applyCb()})
const applyCb = () => {let newValue = runner()
if (newValue !== oldValue) {cb(newValue, oldValue)
oldValue = newValue
}
}
if (immediate) {applyCb()
} else {oldValue = runner()
}
}
let push = Array.prototype.push
let arrayMethods = Object.create(Array.prototype)
arrayMethods.push = function (...args) {console.log(this)
push.apply(this, [...args])
// 这里须要调用 notify 办法
// notify 办法挂载在 createReactive 函数内的 dep 实例上,批改为挂载到 target 上
// 这里通过 this 就能够拿到 notify 办法
this._dep && this._dep.notify()}
// set 示例:let count = ref(0)
/* // count.v 新增属性,不会有响应式变动
document.getElementById('add').addEventListener('click', function () {if (!count.v) {count.v = 0}
count.v++
})
let str
let stop = watchEffect(() => {str = `hello ${count.v}`
document.getElementById('app').innerText = str
}) */
document.getElementById('add').addEventListener('click', function () {if (!count.v) {set(count, 'v', 0)
watchEffect(() => {str = `hello ${count.v}`
document.getElementById('app').innerText = str
})
}
count.v++
})
// 数组 push 示例:let arrValue = 0
// set 函数中曾经对依赖进行了一次增加
let countArr = set([], 1, 0)
document.getElementById('addArr').addEventListener('click', function () {
arrValue++
countArr.push(arrValue)
})
watchEffect(() => {str = `hello ${countArr.join(',')}`
document.getElementById('appArr').innerText = str
})
</script>
</html>