published: true date: 2022-2-3 tags: '前端框架 Vue'
mini-vue
本章在之前的章节的基础中实现了一个简单的vue框架,其中响应式的函数有略微变化不过大致原理相同。
致谢Vue Mastery非常好的课程,可以转载,但请声明源链接:文章源链接justin3go.com(有些latex公式某些平台不能渲染可查看这个网站)
<div id="app"></div> <script> //vdom部分 function h(tag, props, children) { return { tag, props, children, }; } function mount(vnode, container) { const { tag, props, children } = vnode; const el = (vnode.el = document.createElement(tag)); if (props) { for (let key in props) { const value = props[key]; if (key.startsWith("on")) { el.addEventListener(key.slice(2).toLowerCase(), value); } else { el.setAttribute(key, value); } } } if (children) { if (Array.isArray(children)) { children.forEach((child) => { if (typeof child === "string") { el.append(child); } else if (typeof child === "object") { mount(child, el); } }); } else { el.append(children); } } container.append(el); } function patch(n1, n2) { if (n1.tag === n2.tag) { const el = (n2.el = n1.el); //diff props const oldProps = n1.props || {}; const newProps = n2.props || {}; //添加新的属性或更改原来已有但变化了的属性 for (let key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { el.setAttribute(key, newValue); } } //移除新属性中没有的属性 for (let key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } //diff children const oldChildren = n1.children; const newChildren = n2.children; if (typeof newChildren === "string") { if (typeof oldChildren === "string") { if (oldChildren !== newChildren) { el.innerHTML = newChildren; } } } else if (typeof oldChildren === "string" && Array.isArray(newChildren)) { el.innerHTML = ""; newChildren.forEach((child) => mount(child, el)); } else if (Array.isArray(oldChildren) && Array.isArray(newChildren)) { const minLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < minLength; i++) { patch(oldChildren[i], newChildren[i]); } if (oldChildren.length === minLength) { for (let i = minLength; i < newChildren.length; i++) { mount(newChildren[i], el); } } else { for (let i = minLength; i < oldChildren.length; i++) { el.removeChild(oldChildren[i].el); } } } } else { //replace } } //reactivity部分 let activeEffect = null; class Dep { subs = new Set(); depend() { if (activeEffect) { this.subs.add(activeEffect); } } notify() { this.subs.forEach((sub) => sub()); } } function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { if (!targetMap.has(target)) { targetMap.set(target, new Map()); } const depMap = targetMap.get(target); if (!depMap.has(key)) { depMap.set(key, new Dep()); } return depMap.get(key); } const reactiveHandlers = { get(target, key, receiver) { // dep const dep = getDep(target, key); dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); const ret = Reflect.set(target, key, value, receiver); dep.notify(); return ret; }, }; function reactive(raw) { return new Proxy(raw, reactiveHandlers); } //component组件实例 //container要挂载的dom元素 function mountApp(component, container) { let isMounted = false; let oldVdom; watchEffect(() => { if (!isMounted) { //第一次挂载 oldVdom = component.render(); mount(oldVdom, container); isMounted = true; } else { //数据变化,要进行更新 const newVdom = component.render(); patch(oldVdom, newVdom); oldVdom = newVdom; } }); } const App = { data: reactive({ count: 0, }), render() { return h("div", null, [ h( "div", { onClick: () => App.data.count++, }, String(this.data.count) ), ]); }, }; //一个点击自增的计数器 mountApp(App, document.getElementById("app")); </script>