美文网首页
2021-03-06 虚拟dom - 01

2021-03-06 虚拟dom - 01

作者: FConfidence | 来源:发表于2021-03-07 17:38 被阅读0次

入口文件

// 1. 先实现虚拟dom, 主要就是一个对象, 来表述dom节点, jsx
// createElement, h

import { h, render } from './vdom/index';

const vnode = h(
  "div",
  {
    key: "keyCode",
    id: "container",
  },
  h("span", { style: { color: "red" } }, "hello"),
  "dom diff"
);

// 渲染 render: 将虚拟节点转换成为真实的dom节点, 最后插入到app元素中
render(vnode, document.getElementById('app'))

// 来一个新的节点 替换老的节点
const newVnode = h('h2', {}, 'hello world');

/**
 * <div id="container"><span style="color:red">hello</span>dom diff</div>
    h(
      'div',
      {
        key: 'keyCode',
        id: 'container'
      },
      h(
        'span',
        { style: { color: 'red' } },
        'hello'
      ),
      'dom diff'
    )
    
    生成的虚拟节点对象
    {
      type: 'div',
      props: { id: 'container' },
      children: [
        {
          type: 'span',
          props: {
            style: {
              color: red
            }
          },
          children: [],
          text: 'hello'
        },
        {
          type: '',
          props: {},
          children: [],
          text: 'dom diff'
        }
      ]
    }
 */

h方法的实现 (创建虚拟节点)

import { vnode } from './vnode';

/**
 * 创建元素节点
 * @param {String|Object} type 标签类型
 * @param {Object} props 标签对应属性
 * @param  {...any} children 子节点数组
 */
export default function createElement(type, props, ...children) {
  let key = null;
  if (props.key) {
    key = props.key;
    delete props.key;
  }

  // 将不是虚拟节点的子节点变成虚拟节点
  children = children.map((child) => {
    if (typeof child !== 'string') {
      return child;
    }
    return vnode(null, null, null, null, child);
  });

  return vnode(type, key, props, children);
}


创建虚拟节点 vnode.js

/**
 * 创建虚拟节点
 * @param {String|Object} type 标签类型
 * @param {any} key 节点key
 * @param {Object} props 组件属性
 * @param {Array} children 组件子节点
 * @param {String} text 组件文本值
 * @returns 
 */
function vnode(type, key, props, children, text) {
  return {
    type,
    props,
    key,
    children,
    text,
  };
}

export {
  vnode
}

patch (render) 将虚拟节点转化成为真实节点

/**
 * 将虚拟节点渲染到真实的dom节点上
 * @param {Object} vnode 虚拟节点
 * @param {HTMLElement} container 真实dom节点 
 */
function render(vnode, container) {
  // 1. 将虚拟节点转化成为真实的节点
  const realElement = createDomElementFromVnode(vnode);

  container.appendChild(realElement);
}

/**
 * 根据虚拟节点生成真实的html节点
 * @param {Object} vnode 虚拟节点
 */
function createDomElementFromVnode(vnode) {
  const {
    type,
    props,
    children,
    key,
    text
  } = vnode;
  // 表示是一个标签节点
  if (type) { // 标签节点
    // 建立虚拟节点和真实节点的关系, 后面可以用来更新真实dom
    vnode.domElement = document.createElement(type);

    // 根据当前虚拟节点的属性, 去更新真实的dom元素
    updateProperties(vnode);

    // 针对子节点的渲染
    children.forEach((childVnode) => {
      // 将childVnode变成真实节点, 插入到当前节点中
      render(childVnode, vnode.domElement); // 递归渲染子节点
    });
  } else { // 文本节点
    vnode.domElement = document.createTextNode(text);
  }
  return vnode.domElement;
}

/**
 * 更新真实dom的属性
 * @param {Object} newVnode 新的虚拟节点
 * @param {Object} oldProps 之前的属性
 */
function updateProperties(newVnode, oldProps = {}) {
  // 真实的dom节点
  const domElement = newVnode.domElement;
  // 当前虚拟节点的属性
  const newProps = newVnode.props;

  /*
    div[a=1 b=2 c=3]
    属性的增加和缺失情况: div[a=1 d=4]
  */

  // 1. 如果老节点有属性, 新节点是没有的, 那么表示本次更新, 需要删除该属性
  for (let oldPropName in oldProps) {
    if (!newProps[oldPropName]) {
      delete domElement[oldPropName]
    }
  }

  // 如果新的里面有style, 老的里面也有style, 但是老的style里面有background, 但是新的里面没有
  const newStyleObj = newProps.style || {};
  const oldStyleObj = oldProps.style || {};

  for (let propName in oldStyleObj) {
    if (!newStyleObj[propName]) {
      // 老节点更新成为新节点之后, 某个样式的属性没有了, 将其值为空即可
      domElement.style[propName] = '';
    }
  }

  
  // 2. 如果老节点里面, 新的里面有, 表示新节点的属性直接覆盖老节点的属性
  for (let newPropName in newProps) {
    // 多余考虑 @click EventListener
    if (newPropName === 'style') {
      const styleObj = newProps.style;
      for (const s in styleObj) {
        domElement.style[s] = styleObj[s];
        // domElement.style.setProperty(s, styleObj[s]);
      }
    } else {
      domElement[newPropName] = newProps[newPropName];
    }
  }
}

export {
  render,
}

相关文章

网友评论

      本文标题:2021-03-06 虚拟dom - 01

      本文链接:https://www.haomeiwen.com/subject/gecjqltx.html