WPF、もとい MVVM 信者としては、どうしても pure な Class で ViewModel を作りたいというのがあって*1、React にその思想を持ち込んでみた。
React では往々にして Container Component といわれる MVC の Controller みたいなものと Presentation Component と言われる View みたいなものを多層に重ねて UI を作る。V と C が交互に折り重なるように見えるので、ミルフィーユ方式と呼びたい。
それに対して、MVVM(って言っていいのかな)では、ViewModel という名のただのインスタンスのオブジェクトグラフが1本あって、それとは別に View がある。で、この View は ViewModel のオブジェクトグラフとトラバーサルして変換するような形で作られる。XAML がわからない人は XSLT を想像してください。ああいう感じです(ちょっと違うけど)。
ミルフィーユ方式ではどうしても状態管理と VDOM 生成の責務がべったりしがち、ということがあってあまり好きではないんです。Controller が View を知ってる、みたいになってしまって気持ちよくない。それに対して MVVM の View はロジック(ViewModel)に対してとことん非破壊的なんですね。これがよい。ViewModel は好きなように書いて、View がそれに合わせる。理想じゃないですか。
で、VDOM のポテンシャルを活かせばそれくらいできるでしょう、と午前2時くらいの脳がひらめいたのでちょろっと書いてみました。
まず、なんの変哲もない木構造のクラスを定義します。
export abstract class Node { constructor(public value: string) {} } export class Tree extends Node { constructor(value: string, public children: Node[]) { super(value); } } export class Leaf extends Node { }
こう、pure な感じで普通に書くわけですね。
で、それをレンダリングする View を書きます。
class TreeView extends ScopedComponent<{ root: Node }, {}> { render() { const {root} = this.props; return ( <ul> <DataTemplate type={Tree}> { ({content}: { content: Tree }) => <li> {content.value} <ul> { content.children.map((node, i) => { return <ContentControl key={i} content={node} />; }) } </ul> </li> } </DataTemplate> <DataTemplate type={Leaf}> { ({content}: { content: Leaf }) => <li> {content.value} </li> } </DataTemplate> <ContentControl content={root} /> </ul> ); } }
実装はこちらにおいてあります。XAML に恋い焦がれている方はどうぞ。PoC なので、スコープが hierarchical cascade じゃないとかで全然実用的ではないですが。