web123456

You Yuxi - Write a mini vue

<div id="app"></div> <script> function h(tag, props, children) { return { tag, props, children } } function mount(vnode, container) { // Parameters: (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, when both are div tags const el = n1.el = n2.el if (n1.tag == n2.tag) { const oldProps = n1.props || {} const newProps = n2.props || {} // Props is set to the property of the new node. When the two props are not equal 1.1 for (const key in newProps) { const oldValue = oldProps[key] const newValue = newProps[key] if (oldValue !== newValue) { el.setAttribute(key, newValue) } } // Delete props, when only the old node has props 1.2 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key) } } const oldChildren = n1.children const newChildren = n2.children //textcontent is directly the value of the new node. When the new node has no child nodes 2.1 if (typeof newChildren == 'string') { if (typeof oldChildren == 'string') { if (newChildren !== oldChildren) { el.textContent = newChildren } } else { el.textContent = newChildren } } else { // The child nodes of the new node are mounted, when only the new node has children 2.2 if (typeof oldChildren == 'string') { el.innerHTML = '' newChildren.forEach(item => { mount(item, el) }) } else { //Compare, when both have child nodes spans 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, when the two doms are different } } let activeEffect; // Dependency collector 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 is the Map type. The Map that stores the object to respond to. /** * { * [target]: Map() *} */ let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } // Set Dep listening class for each key 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) // Collect dependencies 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>