タグ別アーカイブ: react-router

react-router v2.xとcontext

※この記事は
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