想定しているシチュエーション
非SPA環境で個別にマウントされるコンポーネントがそれぞれで小さくFluxするような環境。
SPAガッツリ組むのでないなら、Fluxフレームワークは不要だと思っていて、とは言えオレオレ構成も行き過ぎると害になる。
その辺のバランスをとって、次のような構成がいいのではないか、と考えてみた。
考え方
- コンテナがEventEmitterを1つ保持する
- コンテナはEventEmitterの各種イベントをListenする
- コンテナはpropsとstateを区別して扱い、stateを更新する
- コンテナはコンポーネントを一つだけ描画する
- コンポーネントはpropsとして渡されたEventEmitterを発火させる
- コンポーネントはEventEmitterをListenしない
- コンポーネントはpropsのみ扱う
コード
// src/components/header.js
export default ({title, emitter}) => (
<header>
<h1 onClick={e => emitter.emit("changeTitleToBar")}>{title}</h1>
</header>
);
// src/containers/header-container.js
import Header from "../components/header";
import {Component} from "react";
import {EventEmitter} from "events";
export default class HeaderContainer extends Component {
constructor(props) {
super(props);
this.state = {title: "foo"};
this.emitter = new EventEmitter;
// EventEmitter の PubSub
// 親の責務は setState/replaceState で State 更新
// ここを Atomic にしないと完全に見失う
this.emitter.on("changeTitleToBar", () => {
this.replaceState({title: "bar"});
});
}
render(){
// 適当にマッピングして子に渡す
return <Header ...{Object.assign({}, this.props, this.state, {emitter: this.emitter})}/>
}
}
// src/main.js
import HeaderContainer from "./containers/header-container";
import ReactDOM from "react-dom";
ReactDOM.render(<HeaderContainer />, document.querySelector(".headerContainer"));
利点
- 導入が簡単
- フレームワークで隠蔽する箇所が無いので、読みやすい
欠点
- emitterを全ての子Componentにバケツリレーする必要がある
- なんでもできて割れ窓っぽい
改善案としては
return <Header ...{Object.assign({}, this.props, this.state, {dispatch: this.emitter.emit.bind(this.emitter)})}/>
などとすると 発火の関数だけ渡すと 子がlisten できなくなる
運用してみたか
いいえ。
とはいえ https://github.com/mizchi/flumpt は、この発想を Reactのcontextという機能を使ってemitterバケツリレーを隠蔽し、それにアクセスするDSLを足しただけ。
ただflumptは色んな物を隠蔽しすぎてサーバーサイドレンダリングに対応できてないので、これぐらいが適当な気がする。