13
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

Organization

React.js の Context API で Private Route を作る

Reactでログイン機能があるSPAを開発する際に、特定のページ(ルート)を秘匿したい場合があると思います。
今回はReact v16から実装されたContext APIを使ってプライベートなルートを作成します。

Context APIとは

Context APIはProviderとConsumerという機構を使い、異なるコンポーネント間でデータの共有を可能にするものです。大まかな概要は以下の通りです。

  • createContextはProviderとConsumerというコンポーネントのペアを返す
  • Providerはデータとコールバックを保有するコンポーネント
  • ConsumerはProviderのデータを購読しコンポーネントにデータを渡す

環境

ライブラリのバージョン

Library version
react 16.2.0
react-router-dom 4.3.1

ディレクトリ構成

Reactではディレクトリ構成やコンポーネントの分割に規制がなく、アプリケーションの要件に応じて設計することができます。
以下は今回、解説に使うアプリケーションのディレクトリ構成です。あくまで例ですので、実際の開発では適宜設計してください。

index.js
  /contexts
    auth.js
  /routes
    home.js
    mypage.js

1. Contextを作成する

/contextsディレクトリにauth.jsファイルを作成しAuthContextを作成します。

// contexts/auth.js
import React, { Component } from 'react';
// Contextの生成
const AuthContext = React.createContext();
// ContextにはProviderとConsumerというコンポーネントが含まれる
const AuthProvider = AuthContext.Provider;
const AuthConsumer = AuthContext.Consumer;

export { AuthProvider, AuthConsumer };

AuthContextにログイン状態を表すisAuthというデータを管理させるため、Providerをカスタマイズします。

// contexts/auth.js
...
class AuthProvider extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isAuth: false
    }
  }
  // Providerのvalueを設定する。Consumerはこのvalueを購読する。
  render() {
    return (
      <AuthContext.Provider
        value={{
          isAuth: this.state.isAuth
        }}
      >
        {this.props.children}
      </AuthContext.Provider>
    );
  }
}
...

2. Contextからデータを取り出す

AuthContextからisAuthを取り出しコンポーネントに反映させてみます。
Headerというコンポーネントを作成し、ログイン状態に応じてテキストを表示させます。

ProviderでHeaderコンポーネントをラップし、コンポーネント側ではConsumerを使ってデータを取り出します。

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { AuthProvider, AuthConsumer } from './contexts/auth';
// ProviderでHeaderコンポーネントをラップ
const App = () => (
  <AuthProvider>
    <Header />
  </AuthProvider>
);
// Headerコンポーネント中でConsumerからデータを取り出す
const Header = () => (
  <AuthConsumer>
    {({ isAuth }) => (
      <>
        <h1>Header</h1>
        {isAuth ? (
          <p>ログインしているよ!</p>
        ) : (
          <p>ログインしていないよ!</p>
        )}
      </>
    )}
  </AuthConsumer>
);

ReactDOM.render(<App />, document.getElementById('root'));

3. Providerに状態を変更するコールバックを作成

ログイン、ログアウトボタンを作成するために、AuthProviderにログイン状態を切り替えるためのコールバック関数を作成します。
今回はfetch等のAPI通信は割愛します。

// contexts/auth.js
...
class AuthProvider extends Component {
  constructor(props) {
    ...
    // thisをバインド
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
  }
  login() {
    // 実際にはここでAPI等を使ってログイン処理を行う
    setTimeout(() => this.setState({ isAuth: true }), 1000);
  }
  logout() {
    this.setState({ isAuth: false });
  }
  render() {
    return (
      <AuthContext.Provider
        value={{
          isAuth: this.state.isAuth,
          login: this.login,
          logout: this.logout
        }}
      >
        {this.props.children}
      </AuthContext.Provider>
    );
  }
}
...

続いてHeaderコンポーネントにログイン/ログアウトボタンを実装します。

// index.js
...
const Header = () => (
  <AuthConsumer>
    {({ isAuth, login, logout }) => (
      <>
        <h1>Header</h1>
        {isAuth ? (
          <p>ログインしているよ!</p>
          <button onClick={logout}>ログアウト</button>
        ) : (
          <p>ログインしていないよ!</p>
          <button onClick={login}>ログイン</button>
        )}
      </>
    )}
  </AuthConsumer>
);
...

4. プライベートなルートを作成

次はreact-routerを使ってプライベートなルートを作成していきます。
Headerコンポーネントに続いて、react-routerを使ってルーティングを設定します。

// index.js
...
import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom';
import Home from './routes/home';
import MyPage from './routes/mypage';
// Headerコンポーネントを取り除き、ルーティングを設定
const App = () => (
  <BrowserRouter>
    <AuthProvider>
      <Header />
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/mypage" component={MyPage} />
      </Switch>
    </AuthProvider>
  </BrowserRouter>
);
...

続いて、プライベートなルートを作成するための、PrivateRouteコンポーネントを作成します。

// index.js
...
// プライベートなルートを設定するためのコンポーネント
const PrivateRoute = ({ component: Component, ...rest }) => (
  <AuthConsumer>
    {({ isAuth }) => (
      <Route
        render={props =>
          isAuth ? (
            <Component {...props} />
          ) : (
            <Redirect to="/" />
          )
        }
        {...rest}
      />
    )}
  </AuthConsumer>
);
// 秘匿したいルートをPrivateRouteで置き換える
const App = () => (
  <BrowserRouter>
    <AuthProvider>
      <Header />
      <Switch>
        <Route exact path="/" component={Home} />
        <PrivateRoute path="/mypage" component={MyPage} />
      </Switch>
    </AuthProvider>
  </BrowserRouter>
);
...

未ログイン状態でmypageにアクセスすると、トップページにリダイレクトされることが確認できると思います。

大規模なアプリケーションなど厳格な状態管理が必要とされるケースでは、Redux等の状態管理フレームワークが引き続き有用に思いますが、中小規模のアプリケーションにおいてはContextを用いるのも良い選択肢ではないでしょうか。

参照サイト
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
13
Help us understand the problem. What are the problem?