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を用いるのも良い選択肢ではないでしょうか。