Virtual DOM 的原理與實現

只貼代碼 不解釋過程 勿噴

博客 文章地址;
github地址html

環境搭建

1.克隆node

$ git clone https://github.com/cvgellhorn/webpack-boilerplate.git
$ npm install 
$ npm install @babel/plugin-transform-react-jsx --save-dev
複製代碼

2.配置babelreact

"plugins": [
    ...其餘配置
    [
        "@babel/plugin-transform-react-jsx",
        {
            "pragma": "dom" // 這裏表示生成的jsx 函數
        }
    ]
    ...其餘配置
]
複製代碼

代碼 index.js

/* * @Author: bucai * @Date: 2019-10-16 20:54:45 * @LastEditors: bucai * @LastEditTime: 2019-10-17 10:11:38 * @Description: vdom */

// 生成的虛擬dom的節點 
function dom(type, props, ...children) {
  return {
    type,
    props,
    children
  }
}

/** * 生成真實的dom樹 * @param {object} domObj node節點 */
function generateDom(domObj) {
  let $el;
  if (domObj.type) {
    $el = document.createElement(domObj.type);
  } else {
    $el = document.createTextNode(domObj);
  }

  if (domObj.props) {
    Object.keys(domObj.props).forEach(key => {
      $el.setAttribute(key, domObj.props[key]);
    });
  }

  if (domObj.children) {
    domObj.children.forEach(child => {
      $el.appendChild(generateDom(child));
    });
  }

  return $el;
}
/** * 對比節點是否改變 * @param {object} nodea 節點A * @param {object} nodeb 節點B */
// node 發生改變
function isNodeChange(nodea, nodeb) {
  // 若是nodea.type 都不爲空表示是 元素節點
  if (nodea.type !== undefined && nodeb.type !== undefined) {
    return nodea.type !== nodeb.type;
  }
  // 若是是其中又一個是文本節點就判斷字符串 
  return nodea !== nodeb;
}
/** * 是否參數改變 * @param {object} propa attrlist * @param {*} propb attrlist */
// props change
function isPropsChange(propa, propb) {
  // 統一化
  const oldProps = propa || {};
  const newProps = propb || {};
  // 獲取參數的key
  const oldKeys = Object.keys(oldProps);
  const newKeys = Object.keys(newProps);
  // 長度不一樣就說明改變了
  if (oldKeys.length !== newKeys.length) {
    return true;
  }
  // 當長度一致的時候
  if (oldKeys.length === 0) {
    return false;
  }
  // 遍歷key 來對比是否改變
  for (let i = 0; i < oldKeys.length; i++) {
    const oldkey = oldKeys[i];
    const newkey = newKeys[i];
    // 對key進行遍歷
    // if (oldkey !== newkey) { // // 這裏沒有意義
    // return true;
    // }
    if (oldProps[oldkey] !== newProps[newkey]) {
      return true;
    }
  }
  // 若是上面都不符合就說是沒有更改的
  return false;
}

/** * 虛擬DOM對比函數 * @param {HTMLElement} $parent 父節點 * @param {object} oldNode 舊的節點對象 * @param {object} newNode 新的節點對象 * @param {number} index 子節點的下標 */
function vDom($parent, oldNode, newNode, index = 0) {
  const $currlenEl = $parent.childNodes[index];
  // 舊的不存在就直接添加到dom中
  if (!oldNode) {
    // append
    return $parent.appendChild(generateDom(newNode));
  }
  // 新的不存在就直接移除舊的節點
  if (!newNode) {
    // REMOVE
    return $parent.removeChild($currlenEl);
  }
  // 判斷節點是否改變
  // node change
  if (isNodeChange(oldNode, newNode)) {
    return $parent.replaceChild(generateDom(newNode), $currlenEl);
  }
  // 節點相同就沒問題
  // no change textNode
  if (oldNode === newNode) {
    return;
  }

  // change props
  const oldProps = oldNode.props || {};
  const newProps = newNode.props || {};

  if (isPropsChange(oldProps, newProps)) {
    const oldPropsKey = Object.keys(oldProps);
    const newPropsKey = Object.keys(newProps);

    // 若是新的props 爲空就將old所有移除
    if (newPropsKey.length === 0) {
      oldPropsKey.forEach(key => {
        $currlenEl.removeAttribute(key);
      });
    } else {

      const propsKeySet = new Set([...oldPropsKey, ...newPropsKey]);

      propsKeySet.forEach(key => {

        if (oldProps[key] === undefined) {
          $currlenEl.setAttribute(key, newProps[key]);
        } else if (newProps[key] === undefined) {
          $currlenEl.removeAttribute(key);
        } else if (newProps[key] !== oldProps[key]) {
          $currlenEl.setAttribute(key, newProps[key]);
        }

      });
    }
  }
  // 子節點
  // children change
  const oldChildren = oldNode.children || [];
  const newChildren = newNode.children || [];

  if (oldChildren.length || newChildren.length) {
    const maxLen = Math.max(oldChildren.length, newChildren.length);
    for (let i = 0; i < maxLen; i++) {
      vDom($currlenEl, oldChildren[i], newChildren[i], i);
    }
  }

}

// 原來的dom樹
const profile = (
  <div> <h3 class="aaaa">123</h3> <p>123</p> </div>
);
// 新的dom樹
const profileChange = (
  <div class="b"> <h3 class="bucai" data-user-id="{a:1}">222</h3> <span>xxxxx</span> </div>
);
// 先生成一個真實的dom樹 並將他添加到html中
const $el = generateDom(profile);
const $app = document.querySelector('#app');
$app.appendChild($el);

// 延時,方便查看
setTimeout(() => {
  // 調用虛擬dom 對比兩棵樹
  vDom($app, profile, profileChange);
}, 3000);


複製代碼
相關文章
相關標籤/搜索