前言
前段时间读了几遍Vue3响应式核心的源码 对vue3响应式核心 明白了依赖收集和响应式原理 今天自己动手简单的实现一下 加深一下理解
响应式核心
const targetMap = new WeakMap()
let activeEffect
// track ==> 依赖追踪收集核心
function track(target, type, key) {
console.log('track')
let depsMap = targetMap.get(target);
// 检查是否被追踪
if (!depsMap) {
// 没有被追踪 新建一个并添加进去
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key);
// 检查当前依赖列表是否包含当前key
if (!dep) {
// 没有就代表没被追踪 新建一个
depsMap.set(key, (dep = new Set()))
}
// 检查依赖中是否包含activeEffect
// activeEffect的赋值在 effect 中 而 effect的调用在mount的时候
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
// activeEffect.deps.push(dep)
}
}
function trigger(target, type, key) {
console.log('trigger')
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked 没有被追踪 直接溜溜球
return;
}
// let deps = []
// if(key !== undefined){
// deps.push(depsMap.get(key))
// }
//
let dep = depsMap.get(key)
// 如果有依赖 那就执行 dep在track中设置为一个Set类型 里面保存着effect
if (dep) {
dep.forEach(effect => {
effect.run()
})
}
}
function effect(fn, options) {
const _effect = new ReactiveEffect(fn)
_effect.run()
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
class ReactiveEffect {
constructor(fn, scheduler = null) {
this.fn = fn
this.scheduler = scheduler
}
run() {
activeEffect = this
return this.fn()
}
}
// 所有经过reactive代理的集合
const reactiveMap = new WeakMap()
const isObject = (val) => val !== null && typeof val === 'object';
export function reactive(target) {
const proxy = new Proxy(target, {
// target:目标对象 key:被获取的键 receiver: Proxy或者继承Proxy的对象
get(target, key, receiver) {
// get获取值的时候顺手进行依赖收集
const res = Reflect.get(target, key, receiver)
track(target, "get", key);
// 如果值是个对象 递归
if (isObject(res)) {
reactive(res)
}
return res
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, "set", key, value);
//
return result
}
})
reactiveMap.set(target, proxy)
return proxy
}
export function mount(instance, el) {
// 先清空容器内容
el.innerHTML = ''
effect(function () {
instance.$data && update(el, instance);
})
instance.$data = instance.setup();
// 执行一次render
update(el, instance);
}
function update(container, instance) {
const vnode = instance.render()
patch(container._vnode || null, vnode, container)
container._vnode = vnode
}
function patch(vnode, newVnode, container) {
// 如果两次一样 直接返回
if (vnode === newVnode) {
return;
}
// 首次挂载
if (vnode == null) {
let el = newVnode.el = document.createElement(newVnode.tagName)
console.log(newVnode)
// 如果子节点是数组 递归挂载
if (Array.isArray(newVnode.children)) {
newVnode.children.forEach(c => {
patch(null, c, el)
})
} else {
el.textContent = newVnode.children
}
if (newVnode.props && newVnode.props.onClick) {
el.addEventListener('click', newVnode.props.onClick)
}
container.appendChild(el)
} else {
//不是首次挂载 对比更新
const el = (newVnode.el = vnode.el)
if (Array.isArray(newVnode.children)) {
newVnode.children.forEach((c, idx) => {
patch(vnode.children[idx], c, el)
})
} else {
if (vnode.children == newVnode.children) {
return
}
el.textContent = newVnode.children
}
}
}
function h(tagName, props, children) {
const vnode = {
tagName,
props,
children,
el: null
}
return vnode
}
import { reactive, h } from './index.js'
const App = {
$data: null,
setup() {
let count = reactive({num: 0})
const handleClick = () => {
count.num++
}
return {
count,
handleClick
};
},
render() {
// `
// <div>
// <p>{{count.num}}</p>
// <button @click="handleClick"> ADD </button>
// </div>
// `
//
const _ctx = this.$data
const _cache = []
return h('div', null, [
h('p', null, _ctx.count.num),
h('button', {
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
}, 'ADD')
])
}
}
mount(App, document.body)
浏览器上跑一下看效果
总结
vue3的设计真的是让人眼前一亮 捋顺这些东西也是让人头秃 QAQ 后期有空在把详细的解释注上