React Router と Context API を組み合わせて使う方法について記述しておく。
環境
- React: 16.4.0
- React-Router: 4.2.2
問題
React Router v4 から history.push をするときに、state という引数を渡すことができるようになった。
this.props.router.push({
pathname: "/to",
state: { test: "test" }
});
これは先のコンポーネントではthis.props.localtion.state.test
として取得できるのだが、これをあまりやりたくない。理由はいくつかあって、
- そもそもデータの受け渡しの場所をむやみに増やしたくない
this.props.location
があるかどうかで処理を切り分けないといけない
といった理由があるんだけど、一番大きいのはブラウザをリロードしたときに URL がなんの意味も持たなくなってしまうから。
これを使わずにうまくやる方法をちょっと考えていて、React の Context API と組み合わせる方法を思いついたので書いておく。正直あまり正しい方法じゃない気もしているが。
React Context and React Router
今回の方法の概要は以下のようになる。
- Contextを作成し、親子コンポーネントに渡せるようにする
- 親コンポーネントで子コンポーネントをルーティングする。
Route
コンポーネントのrender内でContext.Providerを呼び出し、親コンポーネントのstateを渡す。- 子コンポーネントでContext.Consumerを呼び出して利用
Context
まず Context を作成する。Context に関しては React の公式ドキュメントを見ればよいと思う。
import { createContext } from "react";
const AppContext = createContext();
export default AppContext;
これで Context が共有できるようになったので親コンポーネント・子コンポーネントでそれぞれ import する。
AppComponent
一番上のコンポーネントはこんな感じ。単純に React Router で BrowserRouter を使っているイメージ。
const App = () => {
return (
<BrowserRouter>
<Route path="/" component={MainPage} />
</BrowserRouter>
);
};
親コンポーネント
重要な部分だけ抜き出して書く。this.state.users はコンポーネントが初期生成されてからどっかから取ってくるデータ。Router.render
の引数はpushされたときと同じ引数を持つので、パラメーターを参照することができる(今回であればprops.match.params.id
)。
class MainPage extends React.Component {
render() {
return (
// ホントはいろいろある
<Switch>
<Route
path="/user/:id"
render={props => {
return (
<AppContext.Provider value={this.state.users[props.match.params.id]}>
<UserPage />
</AppContext.Provider>
);
}}
/>
</Switch>
);
}
}
子コンポーネント
子コンポーネントはこんな感じで。AppContextから受け取ったuserのデータをもとにコンポーネントを生成する。
class UserPage extends React.Component {
render() {
return (
<div>
<AppContext.Consumer>
{user => <UserComponent {...user} />}
</AppContext.Consumer>
</div>
);
}
}
この手法の利点
user/1
などの状態でリロードされても、親コンポーネントがデータを取得したあとにConsumerが受け取るデータが更新されるのでリロードが機能する- 基本的にはどこからアクセスされたかなどを気にする必要はない
- ReactRouterの黒魔術にあまり頼らなくてすむ
問題点
- 親と子がガッツリ結合してしまう
- そもそもこんなことするならflux的にやったほうがいいのでは?