尤雨溪-写一个mini vue

时间:2025-04-07 20:11:08
<div id="app"></div> <script> function h(tag, props, children) { return { tag, props, children } } function mount(vnode, container) { // 参数: (h('span',null,'hello'), '#div') const el = document.createElement(vnode.tag) if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key] el.setAttribute(key, value) } } if (vnode.children) { // content if (typeof vnode.children === 'string') { el.textContent = vnode.children } else { console.log(vnode.children) // tag vnode.children.forEach(child => { mount(child, el) }) } } container.appendChild(el) } function patch(n1, n2) { // patch, 当两个都是div标签的时候 const el = n1.el = n2.el if (n1.tag == n2.tag) { const oldProps = n1.props || {} const newProps = n2.props || {} // props设置为新节点的属性, 当两个props不相等的时候 1.1 for (const key in newProps) { const oldValue = oldProps[key] const newValue = newProps[key] if (oldValue !== newValue) { el.setAttribute(key, newValue) } } // 删除props, 当只有旧节点中有props的时候 1.2 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key) } } const oldChildren = n1.children const newChildren = n2.children //textcontent直接为新节点的值, 当新节点没有子节点的时候 2.1 if (typeof newChildren == 'string') { if (typeof oldChildren == 'string') { if (newChildren !== oldChildren) { el.textContent = newChildren } } else { el.textContent = newChildren } } else { // 新节点的子节点挂载上去, 当只有新节点有子节点的时候 2.2 if (typeof oldChildren == 'string') { el.innerHTML = '' newChildren.forEach(item => { mount(item, el) }) } else { //比较, 当两个都有子节点span的时候 2.3 const commonLength = Math.min(oldChildren.length, newChildren.length) for (let i = 0; i < commonLength; i++) { patch(oldChildren[i], newChildren[i]) } if (newChildren.length < oldChildren.length) { oldChildren.slice(newChildren.length).forEach(item => { el.removeChild(item.el) }) } if (newChildren.length > oldChildren.length) { oldChildren.slice(newChildren.length).forEach(item => { mount(item, el) }) } } } } else { // replace, 当两个dom不相同的时候 } } let activeEffect; // 依赖收集器 class Dep { subscribers = new Set() depend() { if (activeEffect) { this.subscribers.add(activeEffect) } } notify() { this.subscribers.forEach(effect => { effect() }) } } function watchEffect(effect) { activeEffect = effect effect() activeEffect = null } const targetMap = new WeakMap() function getTarget(target, key) { // depsMap为Map类型 里面存储要响应的对象的Map /** * { * [target]: Map() *} */ let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } // 为每一个key设置Dep监听类 let dep = depsMap.get(key) if (!dep) { dep = new Dep() depsMap.set(key, dep) } return dep } const reactiveHandler = { get(target, key, receiver) { const dep = getTarget(target, key) // 收集依赖 dep.depend() return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { const dep = getTarget(target, key) const result = Reflect.set(target, key, value, receiver) dep.notify() return result } } function reactive(raw) { return new Proxy(raw, reactiveHandler) } const App = { data: reactive({ count: 0 }), render() { return h('div', { onClick: () => { this.data.count++ } }, String(this.data.count)) } } function mountApp(component, container) { let isMounted = false let prevVdom watchEffect(() => { if (!isMounted) { prevVdom = component.render() mount(prevVdom, container) isMounted = true } else { const newVdom = component.render() patch(prevVdom, newVdom) prevVdom = newVdom } }) } mountApp(App, document.getElementById('app')) </script>