简单实现vue渲染系统、响应式系统

本文详细介绍了如何实现Vue3中的响应式系统,包括通过`reactive`函数进行数据劫持,以及`watchEffect`来追踪数据变化。同时,展示了自定义的渲染函数`h`、`mount`和`patch`,用于创建虚拟DOM并进行DOM更新。通过实例,演示了如何将虚拟DOM挂载到实际DOM元素以及动态更新。
摘要由CSDN通过智能技术生成

最终实现效果能够通过render函数创建vnode,并通过mount函数挂载到指定的html元素中
响应式部分通过reactive函数对对象进行数据劫持,在数据get时添加依赖,在数据set时遍历依赖数组,执行数据中的函数

html部分

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app" style="height: 100px;"></div>
  <script src="./renderer.js"></script>
  <script src="./reactive.js"></script>

  <script>
    /* --------------------------------------------------渲染器部分----------------------------------
    const vnode = h('div', {
      class: "vnode",
      onClick: () => {
        console.log(222)
      }
    }, [
      h('div', null, "1.哈哈哈"),
      h('div', null, [
        h('div', { class: "1" }, "1"),
        h('div', { class: "2" }, "2"),
      ]),
    ]);
    mount(vnode, document.getElementById("app"))

    const vnode1 = h('div', {
      class: "vnode",
      onClick: () => {
        console.log(222)
      }
    }, [
      h('div', null, "1.哈哈哈"),
      h('div', null, "2.呵呵呵"),
    ]);

    setTimeout(() => {
      patch(vnode, vnode1);
    }, 2000); */
    /* ---------------------------------------------------------响应式部分-------------------------------------------------------- */
    const info = reactive({
      name: "woaixc",
      age: 18,
    });

    const info2 = reactive({
      name: "woaixc",
      age: 18,
    });

    watchEffect(function () {
      console.log("info", info.age);
    });
    watchEffect(function () {
      console.log("info2", info2.age);
    });

    info.age = 20;


  </script>
</body>

</html>

渲染函数部分 renderer.js

function h(tag, props, children) {
  return {
    tag,
    props,
    children,
  };
}

function mount(vnode, container) {
  // 1.创建元素
  const el = (vnode.el = document.createElement(vnode.tag));
  // 2.创建属性
  if (vnode.props) {
    for (const key in vnode.props) {
      // 2.1 判断如果是事件监听
      if (key.startsWith("on")) {
        el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key]);
      } // 2.1 如果是普通属性
      else {
        el.setAttribute(key, vnode.props[key]);
      }
    }
  }
  // 3.处理children (该处只考虑children为string和array的情况)
  if (typeof vnode.children == "string") {
    el.textContent = vnode.children;
  } else {
    vnode.children.forEach((item) => {
      mount(item, el);
    });
  }
  // 挂载到容器上
  container.appendChild(el);
}

// diff算法
function patch(n1, n2) {
  // n2.el之前未设置是空的,先使三个el相同,引用类型,更改一个全都改
  const el = (n2.el = n1.el);

  // 1.对比新旧节点tag
  // 新旧节点tag不相同则直接删除旧节点,插入新节点
  if (n1.tag !== n2.tag) {
    const n1ParentElement = n1.el.parentElement;
    // 删除旧节点
    n1ParentElement.removeChild(n1.el);
    // 插入新节点
    mount(n2, n1ParentElement);
  } else {
    // 2.对比新旧节点props
    if (n2.props) {
      // 先遍历新的props对象
      for (const key in n2.props) {
        const newValue = n2.props[key];
        const oldValue = n1.props[key];

        // key相同的情况下 新旧props的值不同则替换
        if (newValue !== oldValue) {
          // 如果是事件则绑定事件
          if (key.startsWith("on")) {
            el.addEventListener(key.slice(2).toLowerCase(), newValue);
          } else {
            el.setAttribute(key, newValue);
          }
        }
        // key相同的情况下 新旧props的值相同则维持原样
      }

      // 遍历旧节点中不存在 新节点props中的属性,删除掉
      for (const key in n1.props) {
        const value = n1.props[key];
        if (!(key in n2.props)) {
          // 如果是事件则移除事件
          if (key.startsWith("on")) {
            el.removeEventListener(key.slice(2).toLowerCase(), value);
          } else {
            el.removeAttribute(key);
          }
        }
      }
    }

    // 3.对比新旧节点children
    let newChildren = n2.children || {};
    let oldChildren = n1.children || {};
    // 新节点类型为string
    if (typeof newChildren == "string") {
      // 旧节点类型为string
      if (typeof oldChildren == "string") {
        if (newChildren !== oldChildren) {
          // 设置元素内展示
          el.textContent = newChildren;
        }
      } else {
        // 旧节点类型为array,直接用新节点的string替换
        el.innerHTML = newChildren;
      }
    } else {
      // 新节点类型为array
      // 旧节点类型为string
      if (typeof oldChildren == "string") {
        el.innerHTML = "";
        newChildren.forEach((item) => {
          mount(item, el);
        });
      } else {
        // 新旧节点同时为array的情况下
        // 获取公共长度然后遍历比对
        const commonLength = Math.min(oldChildren.length, newChildren.length);
        // 3.1 遍历公共部分
        // [v1,v2,v3,v4]
        // [v2,v1]
        for (const index in commonLength) {
          patch(oldChildren[index], newChildren[index]);
        }
        // 如果新节点children更长则补充节点,如果就节点children更长则删除节点
        if (newChildren.length > oldChildren.length) {
          newChildren.slice(commonLength).forEach((item) => {
            mount(item, el);
          });
        }
        if (newChildren.length < oldChildren.length) {
          oldChildren.slice(commonLength).forEach((item) => {
            el.removeChild(item.el);
          });
        }
      }
    }
  }
}

响应式部分 reactive.js

// 依赖类
class Dep {
  constructor() {
    // 使用set集合作为依赖收集器,防止同一个依赖多次收集
    this.subscribers = new Set();
  }
  // 依赖添加
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
  // 依赖激活,遍历执行依赖中所有effect函数
  notify() {
    this.subscribers.forEach((effect) => {
      effect();
    });
  }
}

let activeEffect = null;
function watchEffect(effect) {
  activeEffect = effect;
  // 执行一次,添加依赖到依赖收集器中
  effect();
  activeEffect = null;
}

// 使用 Map(target: Map(key: dep))这种数据结构来对依赖进行管理
// 使用weakMap的原因是 weakMap的键是弱引用,弱引用指的是weakMap对键的引用不会影响gc回收,当引用的键在外部被置为null,垃圾回收机制会将其回收掉
let targetMap = new WeakMap();
function getDep(target, key) {
  // 根据target 获取Map(key: dep)
  let depsMap = targetMap.get(target);
  // 如果depsMap不存在
  if (!depsMap) {
    // 创建MAP
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  // 根据key 获取 dep类
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}

/* -----------------vue2 Object.defineProperty方式做响应式 */
/* function reactive(target) {
  if (target) {
    // 遍历对象中的属性
    for (key in target) {
      const dep = getDep(target, key);
      let value = target[key];
      // 数据劫持
      Object.defineProperty(target, key, {
        get() {
          //添加依赖
          dep.depend();
          return value;
        },
        set(newValue) {
          value = newValue;
          dep.notify();
        },
      });
    }
    return target;
  }
} */

/* -----------------vue3 通过ES6的Proxy方式做响应式 */
function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      const dep = getDep(target, key);
      dep.depend();
      return target[key];
    },
    set(target, key, newValue) {
      const dep = getDep(target, key);
      target[key] = newValue;
      dep.notify();
    },
  });
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值