主要实现
ref,toRef,toRefs,isRef,toRaw()
reactive ,shallowReactive(),shallowReadonly(),readonly()
watch,computed 等
// 存储副作用函数的桶
const bucket = new WeakMap()
const ITERATE_KEY = Symbol() //存储for...in副作用函数
const reactiveMap = new Map() //每个对象,只能有一个reactive对象,存储这reactive
function reactive(obj) {
const proxy = createReactive(obj)
const existionProxy = reactiveMap.get(obj)
if (existionProxy) return existionProxy // 如果已经存在reactive对象,就返回这个
//解决数组查找方法,中比较会创建新的的reactive对象,导致不相等
reactiveMap.set(obj, proxy) //否则新建的存入reactiveMap
return proxy
}
function shallowReactive(obj) {
return createReactive(obj, true)
}
function readonly(obj) {
return createReactive(obj, false, true)
}
function shallowReadonly(obj) {
return createReactive(obj, true, true)
}
function isReactive(value) {
if (isReadonly(value)) {
return isReactive(value["__v_raw" /* RAW */]);
}
return !!(value && value["__v_isReactive" /* IS_REACTIVE */]);
}
function isReadonly(value) {
return !!(value && value["__v_isReadonly" /* IS_READONLY */]);
}
function isShallow(value) {
return !!(value && value["__v_isShallow" /* IS_SHALLOW */]);
}
function toRaw(observed) {
const raw = observed && observed["__v_raw" /* RAW */];
return raw ? toRaw(raw) : observed;
}
const arrayInstrumentations = {};
// 重写数组查询方法
['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function(...args) {
// this 是代理对象,先在代理对象中查找,将结果存储到 res 中
let res = originMethod.apply(this, args)
if (res === false) {
// res 为 false 说明没找到,在通过 this.raw 拿到原始数组,再去原始数组中查找,并更新 res 值
res = originMethod.apply(this.raw, args)
}
// 返回最终的结果
return res
}
})
let shouldTrack = true;
// 重写数组修改方法
['push'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function(...args) {
shouldTrack = false // 5.7.4避免多个effect中都是要push方法导致报错
let res = originMethod.apply(this, args)
shouldTrack = true
return res
}
})
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
// 拦截读取操作
get(target, key, receiver) {
console.log('get: ', key)
if (key === 'raw') { //获取原始值
return target
}
if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
return Reflect.get(arrayInstrumentations, key, receiver) // 重写数组方法
}
// 非只读的时候才需要建立响应联系
if (!isReadonly && typeof key !== 'symbol') { // symbol表示如果是数组,是要for..of或者values,触发 ///symbol属性读取,不给它收集副作用函数
track(target, key)
}
const res = Reflect.get(target, key, receiver)
if (isShallow) {
return res
}
if (typeof res === 'object' && res !== null) {
// 深只读/响应
return isReadonly ? readonly(res) : reactive(res)
}
return res
},
// 拦截设置操作
set(target, key, newVal, receiver) {
console.log('set: ', key)
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
const oldVal = target[key]
// 如果属性不存在,则说明是在添加新的属性,否则是设置已存在的属性
const type = Array.isArray(target)
? Number(key) < target.length ? 'SET' : 'ADD' // 如果是数组,key小于原始的length是更新,大于是添加新属性
: Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD' // 代理普通对象
// 设置属性值
const res = Reflect.set(target, key, newVal, receiver)
if (target === receiver.raw) { // 优化!防止出现代理对象的原型也是代理对象,会多次执行副作用函数
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
trigger(target, key, type, newVal)
}
}
return res
},
has(target, key) { //代理 in
track(target, key)
return Reflect.has(target, key)
},
ownKeys(target) { //代理for...in
console.log('ownkeys: ')
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY) // 如果是数组,那么length值改变的时候,
// 会重新执行for..in的副作用函数
return Reflect.ownKeys(target)
},
deleteProperty(target, key) { //代理删除对象属性
if (isReadonly) { // 只读不能删除
console.warn(`属性 ${key} 是只读的`)
return true
}
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
trigger(target, key, 'DELETE')
}
return res
}
})
}
function track(target, key) {
if (!activeEffect || !shouldTrack) return
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
deps.add(activeEffect)
activeEffect.deps.push(deps)
}
function trigger(target, key, type, newVal) {
console.log('trigger', key)
const depsMap = bucket.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
const effectsToRun = new Set()
effects && effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
if (type === 'ADD' || type === 'DELETE') { //普通对象新加和删除属性需要 执行for...in的副作用函数
const iterateEffects = depsMap.get(ITERATE_KEY)
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
if (type === 'ADD' && Array.isArray(target)) { // 数组新增值,要出发length的副总用函数
const lengthEffects = depsMap.get('length')
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
if (Array.isArray(target) && key === 'length') { // 修改length要出发,超过新length的属性的副作用函数
depsMap.forEach((effects, key) => {
if (key >= newVal) {
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []
function effect(fn, options = {}) {
const effectFn = () => {
cleanup(effectFn)
// 当调用 effect 注册副作用函数时,将副作用函数复制给 activeEffect
activeEffect = effectFn
// 在调用副作用函数之前将当前副作用函数压栈
effectStack.push(effectFn)
const res = fn()
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并还原 activeEffect 为之前的值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
return res
}
// 将 options 挂在到 effectFn 上
effectFn.options = options
// activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
effectFn.deps = []
// 执行副作用函数
if (!options.lazy) {
effectFn()
}
return effectFn
}
function cleanup(effectFn) { //解决分支问题,document.body.innerText = obj.ok ? obj.text : 'not' 这种情况下的优化
for (let i = 0; i < effectFn.deps.length; i++) {
const deps = effectFn.deps[i]
deps.delete(effectFn)
}
effectFn.deps.length = 0
}
// =================================================================
function ref(val) { // 原始值代理
const wrapper = {
value: val
}
Object.defineProperty(wrapper, '__v_isRef', {
enumerable:false,
value: true
})
return reactive(wrapper)
}
// 解决reactive对象可以是有展开符...不丢失响应
function toRefs(obj) {
const ret = {}
for (const key in obj) { //批量转化为ref
ret[key] = toRef(obj, key)
}
return ret
}
function toRef(obj, key) { //转换为ref的形式
const wrapper = {
get value() {
return obj[key]
},
set value(val) { //结构的值可以设置
obj[key] = val
}
}
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return wrapper
}
//自动脱ref
function proxyRefs(target) {
return new Proxy(target, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver)
return value.__v_isRef ? value.value : value
},
set(target, key, newValue, receiver) {
const value = target[key]
if (value.__v_isRef) {
value.value = newValue
return true
}
return Reflect.set(target, key, newValue, receiver)
}
})
}
// =================================================================
///实现computed
function computed(getter) {
let value // 缓存值
let dirty = true // true表示getter中的多个数据,改变了,需要重新计算
const effectFn = effect(getter, {
lazy: true,
scheduler() {
// console.log('执行')
if (!dirty) {
dirty = true
trigger(newCompute, 'value') //解决computed的值在effect中能正常收集和触发,给computed设置响应式
}
}
})
const newCompute = {
get value() { // 第一个获取computed值
if (dirty) {
value = effectFn()// getter收集到各自的副作用函数
dirty = false
}
track(newCompute, 'value')// 用来收集计算属性的值的代理
return value
}
}
return newCompute // 第一次读取,触发
}
// 实现watch
// =========================
// 递归读取对象所有的属性
function traverse(value, seen = new Set()) {
if (typeof value !== 'object' || value === null || seen.has(value)) return
seen.add(value)
for (const k in value) {
traverse(value[k], seen)
}
return value
}
function watch(source, cb, options = {}) {
let getter
// 如果是监听对象的函数,直接为getter,注意监听函数必须是读取数据
if (typeof source === 'function') {
getter = source
} else {
getter = () => traverse(source)
}
let oldValue, newValue
let cleanup
function onInvalidate(fn) { // 解决上一次的结果的取消,
cleanup = fn
}
const job = () => {
newValue = effectFn()
if (cleanup) {
cleanup()
}
cb(oldValue, newValue, onInvalidate)
oldValue = newValue
}
const effectFn = effect(
// 执行 getter
() => getter(), // 副作用函数
{
lazy: true,
scheduler: () => { //异步执行,调度watch中回调函数的执行时机
if (options?.flush === 'post') { //表示其他副作用函数执行之后再执行
const p = Promise.resolve()
p.then(job)
} else {
job()
}
}
}
)
if (options?.immediate) {
job()
} else {
oldValue = effectFn()
}
}
// 测试
// 定义obj为响应式数据
const obj = reactive({ foo: 1, bar: 0 })
const sumRes = computed(() => obj.foo + obj.bar)//注意sumRes不是响应式
console.log('computed1', sumRes.value)
console.log('computed2', sumRes.value)// 执行
obj.foo++ // 执行执行 trigger(newCompute, 'value') 如果没有下面effect,其实和computed没关系
console.log('computed3', sumRes.value)// 重新执行副作用函数,获取value,值改变
// 这个时候修改sumRes,不会触发任何效果,修改没有用
sumRes.value = 11
console.log('computed4', sumRes.value)// 重新执行副作用函数,获取value,值改变
effect(() => {
console.log(sumRes.value)
})
obj.foo++ // 这个时候修改obj,sumRes才会执行副作用函数才有效
// 异步测试函数
let count = 0
function fetch() {
count++
const res = count === 1 ? 'A' : 'B'
return new Promise(resolve => {
setTimeout(() => {
resolve(res)
}, count === 1 ? 1000 : 100)
})
}
let finallyData
watch(() => obj.foo, async (newVal, oldVal, onInvalidate) => {
let valid = true
onInvalidate(() => {
valid = false
})
const res = await fetch()
if (!valid) return
finallyData = res
console.log(finallyData)
})
obj.foo++
setTimeout(() => {
obj.foo++
}, 200)
/// 脱ref
const newObj = proxyRefs({ ...toRefs(obj) })
console.log(newObj.foo)
console.log(newObj.bar)
newObj.foo = 100
console.log(newObj.foo)