跳至内容

Jixun's Blog 填坑还是开坑,这是个好问题。

Mastodon

一个简易的类 React.createElement 实现

今天突然就讨论到如何快速的建立一个元素,于是又糊了一遍简单的类 createElement 函数…

var h = (function () {
  function deref(fn) {
    return Function.call.bind(fn);
  }

  var slice = deref(Array.prototype.slice);

  // Lodash code starts here
  var MAX_SAFE_INTEGER = 9007199254740991;

  function isObject(value) {
    var type = typeof value;
    return value != null && (type == 'object' || type == 'function');
  }

  function isLength(value) {
    return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
  }

  function isArrayLike(value) {
    return value != null && isLength(value.length) && !isFunction(value);
  }

  function isObjectLike(value) {
    return value != null && typeof value == 'object';
  }
  // Lodash code ends here

  function isFunction(value) {
    return value instanceof Function;
  }

  function makeArray(v) {
    return isArrayLike(v) && typeof v !== 'string' ? slice(v) : [v];
  }

  function isNode(el) {
    return el instanceof Node;
  }

  function isObjectLikeNotArray(value) {
    return isObjectLike(value) && !isArrayLike(value);
  }

  /**
   * 深度对象合并
   * @param {Object} src 需要扩展的对象。
   * @param {Object} [...ext] 待扩展的属性。
   */
  function merge(src) {
    slice(arguments, 1).forEach(function (ext) {
      if (isObjectLikeNotArray(ext)) {
        Object.keys(ext).forEach(function (key) {
          var value = ext[key];
          if (isObjectLikeNotArray(value)) {
            if (!src[key]) {
              src[key] = {};
            }
            merge(src[key], value);
          } else {
            src[key] = value;
          }
        });
      }
    });

    return src;
  }

  function appendChildren(container, children) {
    children.forEach(function (children) {
      makeArray(children).forEach(function (child) {
        if (child || child === '') {
          container.appendChild(isNode(child) ? child : document.createTextNode(child));
        }
      });
    });
    return container;
  }

  /**
   * 建立一个 HTML 元素
   * @param {String|Function} tag 元素标签,或传入 Stateless 组件函数。
   * @param {Object.<String.String|Bool|Number>} [attrs = {}] 元素属性集合。
   * @param {Array.<String|Node>|String|Node} [...children] 子元素集合。
   */
  function h(tag, attrs) {
    var children = slice(arguments, 2);

    // Stateless 组件建立
    if (isFunction(tag)) {
      return tag(Object.assign({ children }, attrs));
    }

    var el = merge(document.createElement(tag), attrs);
    return appendChildren(el, children);
  }

  h.Fragment = function Fragment({ children }) {
    return appendChildren(document.createDocumentFragment(), children);
  };

  return h;
})();

/**
 * 将建立的元素挂载到一个 HTML 节点 (会先暴力清空节点内容)。
 * @param {HTMLElement} mnt 挂载点。
 * @param {Node} node HTML 元素或节点。
 */
function mount(mnt, node) {
  mnt.innerHTML = '';
  mnt.appendChild(node);
}

简单用例

简单的使用例子。部分使用到 JSX 特性写法的需要 Babel + React 插件进行预处理(或等价预处理方案)。

继续阅读 »