Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

React.js使ってみた

先日社内でハッカソンがあり、3日間でTweetDeckみたいなChromeアプリを作った。
初めてReact.jsを使ってみて、ハマった点や疑問に思った点を挙げてみます。

思ったように差分適用されない

  • xhrでアイテムの一覧を取得する
  • 一覧(配列)をsetStateする
  • 新着分のアイテムだけ描画されて「ヤッター!」

って感じを想像してたら、一覧(列)が丸ごと再描画されるという挙動になった。あれ、そういう挙動なんだっけ?
DOMの挿入自体は1回しか走らないから早いは早いんだけど、これじゃない気がする。

チュートリアルの簡易掲示板みたいなのは追加したアイテムだけ再描画されるから何かやり方を間違えてると思うんだけど、まだよく分かってない。

stateの更新でハマる

React.jsではthis.stateを直接操作しても画面には反映されないので、this.setState()を使う必要がある。

最初はこういうコードを書いて、動かなくてハマった。

var newItems = this.state.items.concat();
newItems[0].flagged = true;
this.setState({items: newItems});

Array.concatではディープコピーにならないので、上記の例では結局this.stateを操作するのと同じことになり、setStateで差分が認識されない。
配列内の各要素までコピーするのは面倒だなあと思っていたら、Immutability Helpersというまさにそのためのアドオンがあった。

複製が面倒でその解決策があるというのは良いけど、パッと見で何をしてるのかよく分からなくて黒魔術感がある。ややこしいオブジェクト操作を避けるにはstateは巨大にならないようにするのが良いと思った。ただ、仲介用のオブジェクトを作るならそれを扱うための型情報を定義したくなるから、pure JSONだと扱いづらい印象。

DOMが遠い

JSを書く人なら誰でもDOM Breakpointsを張ってデバッグするけど、React.jsを使うとロジック→Viertual DOM→DOMという流れになるので、DOM Breakpointsを使ったデバッグがやりにくい。

これは慣れればそんなに苦ではないかも。

孫→子→親とイベントを伝播させるのが面倒

チュートリアルではこんな感じでイベントを扱ってる。

var CommentBox = React.createClass({
  handleCommentSubmit: function(comment) {
    // TODO: submit to the server and refresh the list
  },
  render: function() {
    return (
      <div>
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

この方法だと孫→子→親のように伝播させるには、3つのコンポーネントそれぞれにonCommentSubmitとかhandleCommentSubmitを書かないといけない。バブリングのような仕組みはなさそう。

Flux(ちら見しただけ)ではDispatcherとかあるから、複数階層をまたぐイベント処理はそういうので何とかするのが正解?

ループ内でのrefsの設定方法にとまどう

var Items = this.props.items.map(function(item, index) {
  return (
    <Item ref={item.id} />
  );
});

こういうコードを書いたけど、refの使い方として正しいのかよく分からない。これだとthis.refs[item.id]という感じで参照するけど、感覚的にはthis.refs.items[item.id]としたい。

ベストプラクティスがよく分からない

たとえばキーボードイベントを扱いたかったんだけど、Reactでどう扱えば良いのかよく分からなかった。
ぐぐったらこんなディスカッションが見つかったけど、みんなまだ探り探りなんでしょうか。

JSXは良いんだけど…

JSXは良いんだけど、やっぱりlintなど既存のJSツールとの相性が悪い。変換後はJSになるといっても、普段触るのはJSXになってしまうので、ひと手間がけっこうでかい。

JSファイルとは分けてタグの部分だけ分離して管理したい。そういう方法があれば知りたい。

既存プロダクトに組み込む場合、Reactとの共存方法が悩ましい

これはどのライブラリ/フレームワークを使っててもそうなんだけど、既存のコードにうまくReact.jsを差し込む方法を探るにはそれなりに手間がかかると思った。

未来を感じる

とはいえReact.jsを使うと実装はすごく楽だし、Viertual DOMでめちゃ速いです。未来を感じます。 業務に取り込むのは見送ったけど、個人で書く分には積極的に使っていきたいと思いました。

@mizchi

This comment has been minimized.

Copy link

mizchi commented Dec 17, 2014

  • テンプレートはreact-jade 使ってます。とくに不満はないです。
  • 差分適用の失敗は key属性が hitしてないです。サーバーからもらったデータのtitleとかid属性をkeyに使ってみてください。
  • this.state は getter として実装されてるんで代入は出来ないです。
  • 僕はステート全体をlodashの _.cloneDeep({...})を通して配列で保存してます。historyの巻き戻しが出来ます。
  • 一つのclassは親が何かを意識しません。Fluxでは常にDispatcherに対してイベントを投げます。
  • キーボード入力はDispatcher経由で外部イベントとして扱ってます。特定のDOMのkeydownとかだとonKeyDownとかでキャッチしてます。
@koba04

This comment has been minimized.

Copy link

koba04 commented Dec 17, 2014

mizchiさんがだいたい書かれていますが...

思ったように差分適用されない

これはアイテムのIDみたいなものをkeyに指定すると再利用されると思います。
ちなみにDOMの追加削除の確認は、React.addons.Perf.start()、React.addons.Perf.stop()で該当箇所を囲んでReact.addons.Perf.printDOM()すると便利です。

React.addons.Perf.start();
this.setState({list: newList}, () => {
  React.addons.Perf.stop();
  React.addons.Perf.printDOM();
});

http://jsfiddle.net/koba04/Lpeubepw/1/

stateの更新でハマる

var newItems = this.state.items.concat();
newItems[0].flagged = true;
this.setState({items: newItems});

のコードが動かないということですが、実際は下記のように直接stateの値を変更しても反映されます。

http://jsfiddle.net/koba04/u1pc5o35/2/

ReactではshouldComponentUpdateを実装しない限り、setStateでは常にrerenderされて、前後のVirtualDOM Treeの比較を行い差分をDOMに反映します。
なので、stateを直接変更しようが最終的なVirtualDOM Treeに差分があれば反映されるはずです。
もしされないのであれば何か別の原因があったのかもです...。

ちなみにReact.addons.updateはshouldComponentUpdateを===による比較にしたい場合やundoしたりしたいときのために新しいstate(props)オブジェクトを作りたい目的で使うものだと思います。

DOMが遠い

DOMが遠いことが利点でもあるのかなと思ったりもするので、DOMゴリゴリなことをしたい場合は向いていないかもしれないですね。ゲームとか。

孫→子→親とイベントを伝播させるのが面倒

まぁ確かに入れ子が多くなってくると面倒かもしれませんね。
バブリングの仕組みはないので書いてある通り関数を渡していく必要があります。逆にI/Fがわかりやすいというメリットもあるかと...。
または、Fluxのようにするか、BackboneとかEventEmitterを使ってイベント投げるかですね。

ループ内でのrefsの設定方法にとまどう

refsを使って子のComponentをどうにかするよりも、子のComponentがI/Fとして関数を受け付けるようにして親から処理を渡す方が親から子への一方向の流れになってわかりやすいと思います。
(refsの乱用は避けたほうがいいと思ってます)

onHandleClick: function(itemId) {
  // ....
},
render: function() {
  var Items = this.props.items.map(function(item, index) {
    return (
      <Item onClick={this.onHandleClick} />
    );
  });
}

ベストプラクティスがよく分からない

キーボードイベントはcomponentDidMountでaddEventListenerしてcomponentWillMountでremoveEventListenerする感じなのかなと思ってますが、ベストプラクティスはちょっとわからないですね。。。アプリ全体のComponentでstateとして受けて伝播させていくか各Componentで受けて処理するか...。
FluxとかならActionとして投げる感じになるかなとは思いますが。

JSXは良いんだけど…

普通にJSXだけファイル分けてrequireするとかですかね...。
ちなみにreact-futureを見ると将来的にはES6のtemplate stringsで書けるようになるかも...?

既存プロダクトに組み込む場合、Reactとの共存方法が悩ましい

Componentだけにしては結構サイズが大きいのであれですが、facebookでは最初はComment欄だけとか部分的に導入していったみたいでそういうことはやりやすいのかなとは思います。
BackboneとかであればViewの部分を置き換えるとかはやりやすそうですね。

未来を感じる

はい!

@ama-ch

This comment has been minimized.

Copy link
Owner Author

ama-ch commented Dec 18, 2014

@mizchi さん、 @koba04 さん、コメントありがとうございます!
いただいたコメントをもとに諸々見直してみます。stateのくだりは大分勘違いしてたようなので、出直してきます…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.