ブログBlog

    • react-router v2.xとcontext

    • 2016年2月25日
    • React
    • react-router
※この記事は http://qiita.com/kuniken/items/b8777c8342fe1b4ff727 で投稿した内容と同じものです。

react-router また仕様変更

react-router v1.xからv2.xへのバージョンアップ

v0.xからv1.xにあがった時点で色々ありましたが、v2.xでもまた色々あるみたいです。 本家サイトに、v2.0.0 Upgrade Guideがありました。 それによると、大きな変更点は、
  • historyをrouter定義時に必ず指定することになった
  • コンポーネント定義時にmixinsでHistoryを指定しなくなった
  • contextでrouterを使い回すことになった
  • なのでpushStateなんかはcontext.router.push()とかすることになった
  • 今までの実装のままだとコンソールに警告出る、で、次のVerから使えなくなる
というところです。

context

以前はReactの公式ページでは確かcontextについて記述がなかったのですが、いつの頃からか記述が追加されました。 https://facebook.github.io/react/docs/context.html githubの履歴を調べたら最初のコミットが2015年10月10日になっていました。 公式な記述が追加されたってことは、条件付きだったとしても使っていいよ、ってことですよね。

contextの便利なところ

contextを使わない場合、親から子に値を渡す時は、以下のように、
var Parent = React.createClass({
  :
  :
  render: function(){
    return (
      <Child param={this.state.param} />
    );
  }
});

var Child = React.createClass({
  render: function(){
    return (
      <div>{this.props.param}</div>
    );
  }
});
stateとpropsを使って、受け渡しをする必要がありました。 これをcontextに置き換えた場合、子供に値を渡してあげる必要がありません。
var Parent = React.createClass({
  getChildContext: function() {
    return {param: ''};
  },
  :
  :
  render: function(){
    return (
      <Child /> {/* contextの場合、値を渡す必要なし */}
    );
  }
});

var Child = React.createClass({
  render: function(){
    return (
      <div>{this.context.param}</div> {/* contextの値を参照 */}
    );
  }
});

多用しないでね。

公式ページには注意書きがあります。適当に訳してみました。
Note: contextは、先進的かつ実験的な機能です。APIは、将来のリリースで変更する可能性があります。 ほとんどのアプリでは、contextを使用する必要はありません。あなたがReact.jsを使い始めの場合は特に必要ないでしょう。contextを使用すると、データの流れが不明瞭になりますので、コードを理解するのが難しくなります。あなたのアプリでcontextを使うということは、グローバル変数としてstateを受け渡すのと同様のことなのです。 contextを使用する必要がある場合、慎重に行ってください アプリを実装するかライブラリを実装するかによらず、contextの使用は小さな領域に分離し、直接APIを触らないように実装してみてください。それができれば、APIが変更されてもアップグレード作業がより簡単にできます。
とのことです。おかしかったらコメントください。 redux使ってたらあんまり使うことはなさそうな気もします。

react-router v1.xベースのソースをv2.xベースに修正してみる

今日から始めるシリーズで使ってるソースを使って、v2.xベースに修正してみます。

npm インストール

npmを最新にして、react-router、historyを2.0.0にしました。
$ sudo npm install npm -g
$ npm install react-router@2.0.0 history@2.0.0 --save

出るか警告

スクリーンショット 2016-02-11 10.04.20 でました。

いざ修正

1個目の警告は、
  • historyをrouter定義時に必ず指定することになった
これのせいですね。公式サイトだと、 No Default History Using Browser (HTML5 pushState) History Using Custom Histories に詳しい記述があります。 ということで、historyをインポートします。で、Routerに渡してあげます。
//index.js
var ReactRouter = require('react-router');
var Router = ReactRouter.Router;

var hashHistory = ReactRouter.hashHistory;
//browserHistoryの場合
//var browserHistory = ReactRouter.browserHistory;

:

ReactDOM.render(
  <Router history={hashHistory}>{Routes}</Router>,
  document.getElementById('content')
);
次に、2個目以降の警告の部分、
  • コンポーネント定義時にmixinsでHistoryを指定しなくなった
  • contextでrouterを使い回すことになった
  • なのでpushStateなんかはcontext.router.push()とかすることになった
について修正します。もともとのソースは、
//index.js
var Top = React.createClass({
  mixins: [ History ],

  handleSubmit:function(e){
    e.preventDefault();
    /* ログイン処理 */

    //ポータルページへ
    this.history.pushState(null, '/portal');
  },
  render:function(){
    return (
      <div>
        <div className="main">
          <h1>ログイン</h1>
          <form onSubmit={this.handleSubmit}>
            <input placeholder="userid"/>
            <input placeholder="password"/>
            <div style={{textAlign:"center"}}>
              <button type="submit">ログイン</button>
            </div>
          </form>
        </div>
      </div>
    );
  }
});
こんな感じでした。 ので、
  • contextの定義。
  • mixins: [History]を削除。
  • this.history.pushState(null, ‘/portal’);の修正。
が必要になります。 修正すると、以下のようになります。
//index.js
var Top = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },
  handleSubmit:function(e){
    e.preventDefault();
    /* ログイン処理 */

    //ポータルページへ
    this.context.router.push('/portal'); //!!
  },
  render:function(){
    return (
      <div>
        <div className="main">
          <h1>ログイン</h1>
          <form onSubmit={this.handleSubmit}>
            <input placeholder="userid"/>
            <input placeholder="password"/>
            <div style={{textAlign:"center"}}>
              <button type="submit">ログイン</button>
            </div>
          </form>
        </div>
      </div>
    );
  }
});
上記の修正の、
this.context.router.push('/portal');
についてですが、pushState、replaceStateといった〜State()は、公式サイトの、 Programmatic Navigation にあるように、引数も変更されています。 path以外でqueryとかを指定したい場合は、オブジェクトで渡す必要があります。
this.context.router.push({pathname: '/portal', query: '', state: ''});
引数の変更についてはLinkコンポーネントでも同様です。 公式サイトの、 \, onEnter, and isActive use location descriptors によると、query付きで渡したい場合は、
//V1.x
<Link to="/portal" query={{q: "hoge"}} style={{paddingRight: "5px"}}>ポータル</Link>
が、
//V2.x
<Link to={{pathname: "/portal", query: {q: "hoge"}}} style={{paddingRight: "5px"}}>ポータル</Link>
になります。 んー contextが変更する可能性があるというなら、結局自分でmixinを作ったりとかラッパーを作ったりしないといけなくなるのではなかろうか。

移行ツール

公式サイトでは、移行ツールを使った修正方法について案内があります。 Upgrade Automatically with Codemods それによると、 https://github.com/rackt/rackt-codemod に書いてある方法でインストールし、ツールを動かしてと書いてあります。
$ sudo npm install -g jscodeshift
$ npm install rackt-codemod
ツールを動かしてみます。以下な感じ。
$ jscodeshift -t node_modules/rackt-codemod/transforms/history/deprecate-pushState-replaceState.js ./client/scripts/*
結果を見たら、
Processing 5 files...
Spawning 1 workers with 5 files each...
All done.
Results: 0 errors 4 unmodified 0 skipped 1 ok
Time elapsed: 1.99 seconds
とでました。1ファイルpushStateがpushに修正されました。多階層には対応しているのだろうか。。 また、react-router/deprecate-context-history.jsも実行してみましたが、 mixinsはそのままでした。関係ないのかな。

サンプルソース

https://github.com/kunitak/react-router-1to2/tree/v2

この記事を書いた人 : 國田健史

一覧へ戻る

開発についてのお問い合わせはこちら

お問い合わせ

JOIN OUR TEAM

積極採用しています。私たちと一緒に働きませんか?