TypeScriptで快適DOM操作生活を送りたい

TypeScript便利ですね。 自動で型チェックしてくれたり、クラスベース言語ぽい書き方ができたりと、BabelだけでJavaScript書いていた頃の小さなストレスが無くなり、とっても快適なのですが jQueryやReactDOMの ↓ みたいなDOM操作を、生Typescriptでできたら、もっと最高なのにな…(・_・)

jQuery

$('#container').append('<div>Hello world!</div>');

ReactDOM

render(
    <div>Hello world!</div>,
    document.getElementById('container')
);
と思い、次のようなクラスを定義してみました↓

クラスの定義

/**
### DOM
    Substitute of querySelector, appendChild.
*/
export type NodeType = Element | HTMLElement | DocumentFragment | null;

export class DOM {
        private parser: DOMParser = new DOMParser();
        el: NodeType = null;

        constructor(node?: NodeType | string) {
            if (typeof node === 'string' && !/[<>]/.test(node)) {
                this.el = document.querySelector(node);
            } else if (typeof node === 'string' && /[<>]/.test(node)) {
                const collection: NodeList = this.parser.parseFromString(node, 'text/html').body.childNodes;
                const doms: Element[] = [].slice.call(collection);
                if (doms.filter((e) => !(e instanceof Text)).length === 1) {
                    this.el = doms[0];
                } else {
                    this.el = document.createElement('div');
                    const fragment: DocumentFragment = document.createDocumentFragment();
                    for (let [ i, l ]: Array<number> = [ 0, doms.length ]; i < l; i++) fragment.appendChild(doms[i]);
                    this.el.appendChild(fragment);
                }
            } else if (node instanceof Element || node instanceof DocumentFragment) {
                this.el = node;
            } else {
                this.el = document.createDocumentFragment();
            }
        }
        /**
            Substitute of append - childNode:(string | Element | HTMLElement | DocumentFragment)
        */
        append(childNode: NodeType | string): Element | DocumentFragment | null {
            if (typeof childNode === 'string' && this.el) {
                const fragment = document.createDocumentFragment();
                const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
                const doms: Element[] = [].slice.call(collection);
                for (let [ i, l ]: Array<number> = [ 0, doms.length ]; i < l; i++) fragment.appendChild(doms[i]);
                this.el.appendChild(fragment);
            } else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
                this.el.appendChild(childNode);
            } else {
                return this.el;
            }
        }
}

使い方

例1 新しい要素の作成

let container = new DOM('<div id="container"></div>');
new DOM('body').append(container.el);

例2 要素の追加

new DOM('#container').append('<div>Hello world!</div>');

例3 DocumentFragmentによる要素の追加

let fragment = new DOM();
for(let i=0; i<10; i++){
   fragment.append(`
      <div>要素その${i}</div>
   `);
}
new DOM('#container').append(fragment.el);
ReactのようなVirtualDOMの恩恵はありませんが、動的に要素を追加したりするのが格段に楽になりました。
IEでテンプレートリテラルが使えたらもっといいのになあ・・ ちなみに写真は クア・アイナ のハンバーガーとガーリックシュリンプです。

おまけ

要素の上書きをしたい場合は上記クラスに以下のメソッドrewriteを追加
/**
    innerHTML like method - childNode: (string | Element | HTMLElement | DocumentFragment)
*/
rewrite(childNode: NodeType | string): Element | DocumentFragment | null {
    if (typeof childNode === 'string') {
        const fragment = document.createDocumentFragment();
        const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
        const doms: Element[] = [].slice.call(collection);
        for (let [ i, l ]: Array<number> = [ 0, doms.length ]; i < l; i++) fragment.appendChild(doms[i]);
        while (this.el.firstChild) this.el.removeChild(this.el.firstChild);
        this.el.appendChild(fragment);
    } else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
        while (this.el.firstChild) this.el.removeChild(this.el.firstChild);
        this.el.appendChild(childNode);
    } else {
        return this.el;
    }
}
要素を手前に追加したい場合は上記クラスに以下のメソッドprependを追加
/**
    Substitute of prepend - childNode:(string | Element | HTMLElement | DocumentFragment)
*/
prepend(childNode: NodeType | string): Element | DocumentFragment | null {
    if (typeof childNode === 'string') {
        const fragment = document.createDocumentFragment();
        const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
        const doms: Element[] = [].slice.call(collection);
        for (let [ i, l ]: Array<number> = [ 0, doms.length ]; i < l; i++) fragment.appendChild(doms[i]);
        this.el.insertBefore(fragment, this.el.firstChild);
    } else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
        this.el.insertBefore(childNode, this.el.firstChild);
    } else {
        return this.el;
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *