react.js
react-router

react-router v2.xとcontext

More than 1 year has passed since last update.


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.png

でました。


いざ修正

1個目の警告は、



  • historyをrouter定義時に必ず指定することになった


これのせいですね。公式サイトだと、

[https://github.com/ReactTraining/react-router/blob/v2.0.0/upgrade-guides/v2.0.0.md#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コンポーネントでも同様です。

公式サイトの、

<Link to>, 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