// index.js
function isObject(data) {
return data && typeof data === 'object'
}
let targetMap = new WeakMap()
let activeEffect
/**
* Map<target,DepsMap>
* DepsMap<key,dep>
*
* {
* target: {
* key: [effect, effect, effect, effect]
* }
* }
*/
function track(target, key) { // dep.add
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()))
if (!dep.has(activeEffect)) dep.add(activeEffect) // Dep.target && dep.add(Dep.target)
}
function trigger(target, key) { // dep.notify
const depsMap = targetMap.get(target)
if (!depsMap) return
// e => effect
depsMap.get(key).forEach(e => e && e())
}
// 在我们的 demo 里
// effect 执行 -> activeEffect 就有值了(更新页面)-> 触发 getter -> track() -> activeEffect 存起来了
// setter (count.value++) -> trigger -> activeEffect() -> 更新页面
// 总结一下:
// 收集副作用 -> 收集的时间(getter)-> 触发副作用执行(setter)
function effect(fn, options = {}) { // compiler + watcher
const __effect = function(...args) {
activeEffect = __effect
return fn(...args) // this.cb()
}
if (!options.lazy) {
__effect()
}
return __effect
}
/**
* const a = reactive({ count: 0 })
* a.count++
*/
export function reactive(data) {
if (!isObject(data)) return
return new Proxy(data, {
get(target, key, receiver) {
// 反射 target[key] -> 继承关系情况下有坑
const ret = Reflect.get(target, key, receiver)
track(target, key)
return isObject(ret) ? reactive(ret) : ret
},
set(target, key, val, receiver) {
Reflect.set(target, key, val, receiver)
trigger(target, key)
return true
},
deleteProperty(target, key, receiver) {
const ret = Reflect.deleteProperty(target, key, receiver)
trigger(target, key)
return ret
}
})
}
// 基本类型
/**
* const count = ref(0)
* count.value++
*/
export function ref(target) {
let value = target
const obj = {
get value() {
track(obj, 'value')
return value
},
set value(newValue) {
if (value === newValue) return
value = newValue
trigger(obj, 'value')
}
}
return obj
}
export function computed(fn) {//只考虑函数的情况
// 延迟计算 const c = computed(() => `${count.value} + !!!!`); c.value
let __computed
const run = effect(fn, { lazy: true })
__computed = {
get value() {
return run()
}
}
return __computed
}
export function mount(instance, el) {
effect(function() {
instance.$data && update(instance, el)
})
instance.$data = instance.setup()
update(instance, el)
function update(instance, el) {
el.innerHTML = instance.render()
}
}
// html
<!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>
<script type='module'>
import { mount, ref, reactive, computed } from './index.js'
const App = {
$data: null,
setup() {
let count = ref(0)
let time = reactive({ seconds: 0 })
let cc = computed(() => `computed : ${count.value + time.seconds}`)
window.timer = setInterval(() => {
time.seconds++
}, 1000)
setInterval(() => {
count.value++
}, 2000)
return {
count,
time,
cc
}
},
render() {
return `
<h1>How Reactive?</h1>
<p>this is reactive work: ${this.$data.time.seconds}</p>
<p>this is ref work: ${this.$data.count.value}</p>
<p>${this.$data.cc.value}</p>
`
}
}
mount(App, document.querySelector('#app'))
</script>
</head>
<body>
<div id='app'></div>
</body>
</html>
执行结果: