记录一下尤大手把手教学vue3实现原理的代码


来源于b站视频

模板渲染

<html>
  <body>
    <div id="app"></div>
  </body>
</html>
<style>
  .red {
    
    
    color: red;
  }
  .green {
    
    
    color: green;
  }
</style>
<script>
  function h(tag, props, children) {
    
    
    return {
    
    
      tag,
      props,
      children,
    };
  }

  function mount(vnode, container) {
    
    
    const el = document.createElement(vnode.tag);
    vnode.el = el;
    if (vnode.props) {
    
    
      for (const key in vnode.props) {
    
    
        const value = vnode.props[key];
        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);
  }
  const vdom = h("div", {
    
     class: "red" }, [
    h('span', null, 'hello')
  ]);
  mount(vdom, document.getElementById("app"));
</script>

diff patch

在上面的render的基础上,进行patch

  /**
   * 四个假设
   */
  function patch(n1, n2) {
    
    
  	if(!n1) {
    
    
        mount(n2, document.getElementById('app'));
        return;
    }
    //标签相同
    if (n1.tag === n2.tag) {
    
    
      const el = (n2.el = n1.el);
      //props
      const oldProps = n1.props || {
    
    };
      const newProps = n2.props || {
    
    };
      for (const key in newProps) {
    
    
        const oldValue = oldProps[key];
        const newValue = newProps[key];
        if (newValue !== oldValue) {
    
    
          el.setAttribute(key, newValue);
        }
      }
      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 {
    
    
        let commonLength = Math.min(newChildren.length, oldChildren?.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);
            })
        }else if (oldChildren.length > newChildren.length ){
    
    
            oldChildren.slice(newChildren.length).forEach(child =>{
    
    
                el.removeChild(child.el);
            })
        }
      }
    }
  }
  patch(null, vdom);
  const vdom2 = h("div", {
    
     class: "green" }, [h("span", null, "world")]);
  setTimeout(()=>{
    
    
    patch(vdom, vdom2)
  }, 500)
</script>

dep

<script>
    let activeEffect = null;
    class Dep {
    
    
        constructor(value) {
    
    
            this.subscribers = new Set();
            this._value = value
        }
        get value(){
    
    
            this.depend();
            return this._value;
        }
        set value(val) {
    
    
            this._value = val;
            this.notify();
        }
        depend() {
    
    
            if(activeEffect) {
    
    
                this.subscribers.add(activeEffect);
            }
        }

        notify() {
    
    
            this.subscribers.forEach(effect =>{
    
    
                effect();
            })
        }
    }
    function watchEffect(effect){
    
    
        activeEffect = effect;
        effect();
        activeEffect = null;
    }

    const dep = new Dep('hello');
    watchEffect(() =>{
    
    
        console.log(dep.value);
    })
    dep.value = 'change';
    dep.notify(); // effect run
</script>

在上面的Dep基础上,再实现reactive,

reactive

<script>
  let activeEffect = null;
  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;
  }
  let depsMap = new WeakMap();
  const getDep = (target, key) => {
    
    
    let depsForTarget = depsMap.get(target);
    if (!depsForTarget) {
    
    
      depsForTarget = new Map();
      depsMap.set(target, depsForTarget);
    }
    let dep = depsForTarget.get(key);
    if (!dep) {
    
    
      dep = new Dep();
      depsForTarget.set(key, dep);
    }
    return dep;
  };
  const reactiveHandler = {
    
    
    get(target, key, receiver) {
    
    
      const dep = getDep(target, key);
      dep.depend();
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
    
    
      const result = Reflect.set(target, key, value, receiver);
      const dep = getDep(target, key);
      dep.notify();
      return result;
    },
  };
  function reactive(raw) {
    
    
    return new Proxy(raw, reactiveHandler);
  }
  let state = reactive({
    
    
    count: 0,
  });
  watchEffect(() => {
    
    
    // console.log(state.count);
    // console.log(state.msg);
    console.log('msg' in state);
  });
  state.count = 1;
</script>

已经有了响应式构建,依赖收集,副作用处理,节点渲染,下面就是实现一个小型vue了

mini-vue

首先在mount处理节点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);
 }
//创建一个组件
 const App = {
    
    
    data: reactive({
    
     count: 0 }),
    render() {
    
    
      return h("div",{
    
    
        onClick: ()=>{
    
    
            this.data.count++
        }
      }, String(this.data.count));
    },
  };
  function mountApp(component, container) {
    
    
    let prevVdom;
    let isMounted = false;
    watchEffect(()=>{
    
    
        if(!isMounted) {
    
    
            prevVdom = component.render();
            mount(prevVdom, container);
            isMounted = true;
        }else {
    
    
            const newVdom = component.render();
            patch(prevVdom, newVdom);
        }
    })
  }
  mountApp(App, document.getElementById('app'));

ok, that’s all

猜你喜欢

转载自blog.csdn.net/qq_41028148/article/details/127379084
今日推荐