9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-07-02

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;
 
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) as NodeType;
        } else if (typeof node === 'string' && /[<>]/.test(node)) {
            const collection: NodeList = this.parser.parseFromString(node, 'text/html').body.childNodes;
            const doms: Element[] = [].slice.call(collection) as Element[];
            if (doms.filter((e: Element) => !(e instanceof Text)).length === 1) {
                this.el = doms[0];
            } else {
                this.el = document.createElement('div');
                const fragment: DocumentFragment = document.createDocumentFragment();
                for (let [i, l]: 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)
    */
    public append(childNode: NodeType | string): Element | DocumentFragment | boolean {
        if (typeof childNode === 'string' && this.el) {
            const fragment: DocumentFragment = document.createDocumentFragment();
            const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
            const doms: Element[] = [].slice.call(collection) as Element[];
            for (let [i, l]: number[] = [0, doms.length]; i < l; i++) fragment.appendChild(doms[i]);
            this.el.appendChild(fragment);
            return false;
        } else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
            this.el.appendChild(childNode);
            return false;
        } 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の恩恵はありませんが、動的に要素を追加したりするのが格段に楽になりました。

おまけ

要素の上書きをしたい場合は上記クラスに以下のプロパティrewriteを追加

/**
	innerHTML like method - childNode: (string | Element | HTMLElement | DocumentFragment)
*/
public rewrite(childNode: NodeType | string): Element | DocumentFragment | boolean {
	if (typeof childNode === 'string') {
		const fragment: DocumentFragment = document.createDocumentFragment();
		const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
		const doms: Element[] = [].slice.call(collection) as Element[];
		for (let [i, l]: 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);
		return false;
	} else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
		while (this.el.firstChild) this.el.removeChild(this.el.firstChild);
		this.el.appendChild(childNode);
		return false;
	} else {
		return this.el;
	}
}

要素を手前に追加したい場合は上記クラスに以下のプロパティprependを追加

/**
	Substitute of prepend - childNode:(string | Element | HTMLElement | DocumentFragment)
*/
public prepend(childNode: NodeType | string): Element | DocumentFragment | boolean {
	if (typeof childNode === 'string') {
		const fragment: DocumentFragment = document.createDocumentFragment();
		const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
		const doms: Element[] = [].slice.call(collection) as Element[];
		for (let [i, l]: number[] = [0, doms.length]; i < l; i++) fragment.appendChild(doms[i]);
		this.el.insertBefore(fragment, this.el.firstChild);
		return false;
	} else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
		this.el.insertBefore(childNode, this.el.firstChild);
		return false;
	} else {
		return this.el;
	}
}
9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?