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

今回の方法の概要は以下のようになる。

  1. Contextを作成し、親子コンポーネントに渡せるようにする
  2. 親コンポーネントで子コンポーネントをルーティングする。
  3. Routeコンポーネントのrender内でContext.Providerを呼び出し、親コンポーネントのstateを渡す。
  4. 子コンポーネントで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的にやったほうがいいのでは?

参考リンク