Simple implementation of createElement

Just a nice and simple alternative implementation of React.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);
    }

    /**
     * Deep merge
     * @param {Object} src object to be merged
     * @param {Object} [...ext] objects to merge from
     */
    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;
    }

    /**
     * Create an HTML element
     * @param {String|Function} tag Tag name, or Stateless function.
     * @param {Object.<String.String|Bool|Number>} [attrs = {}] Attributes for this element.
     * @param {Array.<String|Node>|String|Node} [...children] Children.
     */
    function h(tag, attrs) {
        var children = slice(arguments, 2);

        // Create Stateless Components.
        if (isFunction(tag)) {
            return tag(Object.assign({ children }, attrs));
        }

        var el = merge(document.createElement(tag), attrs);
        children.forEach(function (children) {
            makeArray(children).forEach(function (child) {
                el.appendChild(isNode(child) ? child : document.createTextNode(child));
            });
        });
        return el;
    }

    return h;
})();

/**
 * Mount elements to a DOM node (previous content will be cleared).
 * @param {HTMLElement} mnt Mount point.
 * @param {Node} node HTML Node.
 */
function mount(mnt, node) {
    mnt.innerHTML = "";
    mnt.appendChild(node);
}

Examples

Use with pre-processor (e.g. babel)

/** @jsx h */
var app = (
    <section>
        <h1>Test: <code>test</code></h1>
        <article className="post">
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc quis ornare velit.</p>
            <p>Nullam interdum, lorem non volutpat auctor, quam ante tempus massa, eget dignissim ligula erat non arcu.</p>
        </article>
    </section>
)

mount(document.getElementById("app"), app);

Without pre-processor

var app = (
    h("section", null,
        h("h1", null,
            "Test: ",
            h("code", null, "test")
        ),
        h("article", { className: "post" },
            h("p", null, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc quis ornare velit."),
            h("p", null, "Nullam interdum, lorem non volutpat auctor, quam ante tempus massa, eget dignissim ligula erat non arcu."),
        ),
    )
);

mount(document.getElementById("app"), app);

Stateless components

/** @jsx h */
// See https://c.jixun.moe/ui for live-demo
(function(root) {
    function UnsafeHtml({ html }) {
        var div = (<i />);
        div.innerHTML = html;
        return div.childNodes;
    }

    function ExternalLink({ className, href, children }) {
        return (
            <a className={className} href={href} target="_blank">
                { children }
            </a>
        );
    }

    function Author({ author, uri }) {
        var AuthorTag = uri ? ExternalLink : "i";
        return (
            <AuthorTag className="author" href={uri}>
                {author || "无名氏"}
            </AuthorTag>
        );
    }

    function Comment({ website, author, uri, text }) {
        return (
            <div className="comment">
                <p>
                    <Author className="author" author={author} uri={website} />
                    (来源: <ExternalLink href={`https://jixun.moe` + uri}>{uri}</ExternalLink>)
                </p>
                <div className="body">
                    <UnsafeHtml html={text} />
                </div>
            </div>
        );
    }

    function App({ comments }) {
        return (
            <div className="comments">
                {comments.map(comment => (
                    <Comment {...comment} />
                ))}
            </div>
        );
    }

    var xhr = new XMLHttpRequest();
    xhr.open("GET", "/latest");
    xhr.onload = xhr.onerror = function () {
        var data;
        try {
            data = JSON.parse(xhr.responseText);
            mount(root, <App {...data} />);
        } catch (error) {
            root.textContent = "获取评论出错。";
        }
    }
    xhr.send();
})(document.getElementById("app"));

This article is also available in: 中文 (简体)

Jixun

Jixun

Just some random guy talking about some random stuff.