Vue3源码学习之路-实现runtime-dom

createRenderer()

创建一个自定义渲染器。通过提供平台特定的节点创建以及更改 API,你可以在非 DOM 环境中也享受到 Vue 核心运行时的特性。

render

用于编程式地创建组件虚拟 DOM 树的函数。

h

创建虚拟 DOM 节点 (vnode)。

先实现基本功能,不考虑细节

使用示例

<!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"></div>
    <script src="./runtime-dom.global.js"></script>
    <!-- <script src="../../../node_modules/@vue/runtime-dom/dist/runtime-dom.global.js"></script> -->
    <script>
      const app = document.getElementById('app');
      const { createRenderer, h, render } = VueRuntimeDOM;

      // // 自定义渲染器,渲染虚拟DOM
      // const renderer = createRenderer({
      //   createElement(el) {
      //     return document.createElement(el);
      //   },
      //   setElementText(el, text) {
      //     el.innerHTML = text;
      //   },
      //   insert(el, container) {
      //     container.appendChild(el);
      //   },
      //   patchProp(el, key, prevValue, nextValue) {
      //     el[key] = nextValue;
      //   },
      // });

      // // h > 创建虚拟 DOM 节点 (vnode)
      // renderer.render(h('h1', { style: { color: 'pink' } }, 'hello world'), app);
      
      render(h('h1', { style: { color: 'pink' } }, 'hello world'), app);
    </script>
  </body>
</html>

渲染器可以用不同的方法将虚拟DOM创建成真实DOM,那么我们只需要提供平台特定的节点创建以及更改 API就行了。

runtime-dom模块

新建目录:packages/runtime-dom,目录结构与之前实现的reactivity模块是一样的,此处省略很多行。。。

将当前调试模块更改为runtime-dom

"scripts": {
   "dev": "node scripts/dev.js runtime-dom -f global"
 },
npm run dev

DOM相关操作API

新建文件:packages/runtime-dom/src/nodeOps.ts

export const nodeOps = {
  // 插入
  insert(child, parent, anchor = null) {
    parent.insertBefore(child, anchor);
  },

  createElement(tagName) {
    return document.createElement(tagName);
  },

  createText(text) {
    return document.createTextNode(text);
  },

  // 移除
  remove(child) {
    const parentNode = child.parentNode;
    if (parentNode) {
      parentNode.removeChild(child);
    }
  },

  // 设置元素文本内容
  setElementText(el, text) {
    el.textContent = text;
  },

  setText(node, text) {
    node.nodeValue = text;
  },

  // 查询
  querySelector(selector) {
    return document.querySelector(selector);
  },

  parentNode(node) {
    return node.parentNode;
  },

  nextSibling(node) {
    return node.nextSibling;
  },
};

DOM属性操作API

packages/shared/src/index.ts

const onRE = /^on[^a-z]/;
export const isOn = (key: string) => onRE.test(key);

新建文件:packages/runtime-dom/src/patchProp.ts

import { isOn } from '@vue/shared';
import { patchAttr } from './modules/attrs';
import { patchClass } from './modules/class';
import { patchEvent } from './modules/events';
import { patchStyle } from './modules/style';

export function patchProp(el, key, prevValue, nextValue) {
  if (key === 'class') {
    // 类名
    patchClass(el, nextValue);
  } else if (key === 'style') {
    // 样式
    patchStyle(el, prevValue, nextValue);
  } else if (isOn(key)) {
    // 事件
    patchEvent(el, key, nextValue);
  } else {
    // 属性
    patchAttr();
  }
}

class类名操作

新建文件:packages/runtime-dom/src/modules/class.ts

export function patchClass(el, nextValue) {
  if (nextValue === null) {
    el.removeAttribute('class');
  } else {
    el.className = nextValue;
  }
}

style操作

新建文件:packages/runtime-dom/src/modules/style.ts

export function patchStyle(el, prevValue, nextValue = {}) {
  // 用新值覆盖
  for (const key in nextValue) {
    el.style[key] = nextValue[key];
  }

  // 移除不存在于新值中的旧值
  if (prevValue) {
    for (const key in prevValue) {
      if (!nextValue[key]) {
        el.style[key] = null;
      }
    }
  }
}

事件操作

事件操作,做了缓存,避免频繁卸载和添加事件,属于性能优化

新建文件:packages/runtime-dom/src/modules/events.ts

export function patchEvent(el, eventName, nextValue) {
  // vei = vue event invokers = vue事件调用
  const invokers = el._vei || (el._vei = {});

  // 是否绑定过事件
  const existingInvoker = invokers[eventName];

  if (nextValue && existingInvoker) {
    // 修改
    existingInvoker.value = nextValue;
  } else {
    let event = eventName.slice(2).toLowerCase();
    if (nextValue) {
      // 添加
      const invoker = (invokers[eventName] = createInvoker(nextValue));
      el.addEventListener(event, invoker);
    } else if (existingInvoker) {
      // 移除
      el.removeEventListener(event, existingInvoker);
      invokers[eventName] = undefined;
    }
  }
}

// 创建自定义事件
function createInvoker(initialValue) {
  const invoker = (e) => invoker.value(e);
  invoker.value = initialValue;

  return invoker;
}

runtime-core

Vue模块划分中,将createRenderer、h方法放在了runtime-core模块中

这里现将基本方法及文件创建出来,下次实现具体方法

创建模块runtime-core,与其他模块一样的目录结构,package.json可以参考Vue源码,这里省略很多行。。

创建:packages/runtime-core/src/renderer.ts

export function createRenderer(renderOptions) {
  const render = (vnode, container) => {};

  return { render };
}

创建:packages/runtime-core/src/h.ts

export function h() {
  
}

创建:packages/runtime-core/src/index.ts

export { createRenderer } from './renderer';
export { h } from './h';

packages/runtime-dom/src/index.ts

import { nodeOps } from './nodeOps';
import { patchProp } from './patchProp';
import { createRenderer } from '@vue/runtime-core';

const rendererOptions = Object.assign({ patchProp }, nodeOps);

export function render(vnode, container) {
  createRenderer(rendererOptions).render(vnode, container);
}

export * from '@vue/runtime-core';

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值