1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
| <div id="app"></div>
<script> function h(tag, props, children) { return { tag, props, children } }
function mount(vnode, container) { const el = (vnode.el = document.createElement(vnode.tag)) if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key] if (key.startsWith("on")) { el.addEventListener(key.slice(2).toLowerCase(), value) } else { el.setAttribute(key, value) } } } if (vnode.children) { if (typeof vnode.children === "string") { el.textContent = vnode.children } else { vnode.children.forEach((child) => { mount(child, el) }) } } container.appendChild(el) }
function patch(n1, n2) { if (n1.tag === n2.tag) { const el = (n2.el = n1.el) const oldProps = n1.props || {} const newProps = n2.props || {}
for (const key in newProps) { const oldVal = oldProps[key] const newVal = newProps[key] if (newVal !== oldVal) { el.setAttribute(key, newVal) } }
for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key) } }
const oldChildren = n1.children const newChildren = n2.children if (typeof newChildren === "string") { if (typeof oldChildren === "string") { if (newChildren !== oldChildren) { el.textContent = newChildren } } else { el.textContent = newChildren } } else { if (typeof oldChildren === "string") { el.innerHTML = "" newChildren.forEach((child) => { mount(child, el) }) } else { 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) { newChildren.slice(oldChildren.length).forEach((child) => { mount(child, el) }) } if (newChildren.length < oldChildren.length) { oldChildren.slice(newChildren.length).forEach((child) => { el.removeChild(child.el) }) } } } } else { } }
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 getDep(target, key) { let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) }
let dep = depsMap.get(key) if (!dep) { dep = new Dep() depsMap.set(key, dep) } return dep } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key) dep.depend() return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { const dep = getDep(target, key) const result = Reflect.set(target, key, value, receiver) dep.notify() return result }, } function reactive(raw) { return new Proxy(raw, reactiveHandlers) }
const App = { data: reactive({ count: 22, }), 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>
|