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

TypeScript便利ですね。

自動で型チェックしてくれたり、クラスベース言語ぽい書き方ができたりと、BabelだけでJavaScript書いていた頃の小さなストレスが無くなり、とっても快適なのですが

jQueryやReactDOMの ↓ みたいなDOM操作を、生Typescriptでできたら、もっと最高なのにな...（・_・）


#### jQuery

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

#### ReactDOM

```tsx
render(
    <div>Hello world!</div>,
    document.getElementById('container')
);
```


と思い、次のようなクラスを定義してみました↓ 

## クラスの定義

```ts
/**
### 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;
        }
    }
}
```

## 使い方

#### 例１　新しい要素の作成

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


#### 例２　要素の追加

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

#### 例３　DocumentFragmentによる要素の追加

```ts
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を追加

```ts
/**
	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を追加

```ts
/**
	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;
	}
}
```