记录一下尤大手把手教学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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值