ReactでDIする
はい、hebiです。
Reactで「useContext」を利用し、DIするための記事を書きたいと思います。
DIとは?
DIとは、Dependency Injectionの略で、依存性注入を意味します。
どゆこと?となったかと思いますので、DIありなしのパターンで説明します。
DIなしパターン
DIなしパターンの構成です。
Loginコンポーネント内でリポジトリを利用しているため、サーバに依存した構成になっています。
つまり、Unitテストを行う際にもサーバが必要となり、試験に必要なデータをサーバを返してしかテストできないため大変です。
DIありパターン
では、DIを利用した構成を見てみましょう。
APコンポーネントでリポジトリをDIコンテナに設定し、LoginコンポーネントはDIコンテナにあるリポジトリを利用するイメージです。
DIすることで、テスト用のリポジトリに置き換えることが容易にでき、テストデータを返せます。
ReactでDI
以下DI構成を作成してみます。Reactの場合、グレー部分のDIコンテナ領域は存在しませんが、概念として記述しました。
リポジトリ作成
リポジトリクラスを作成します。今回は「LoginRepositoryクラス」、「LogoutRepositoryクラス」、「RepositoryFactoryクラス」を作成します。
Interface作成
import { AxiosResponse } from 'axios';
export interface ILoginRepository{
login(userid: string, password: string) : Promise<AxiosResponse>
}
export interface ILogoutRepository{
logout(userid: string) : Promise<void>
}
ログインリポジトリ作成
import {ILoginRepository} from './ILoginRepository'
import axios, { AxiosResponse } from 'axios';
export type User = {
name: string
}
export class LoginRepository implements ILoginRepository{
constructor(){
console.debug('constructor');
}
public login(userid: string, password: string): Promise<AxiosResponse> {
console.debug('LoginRepository login start');
return axios.post<User>(`https://hebiblog.org/login`, {userid, password})
}
}
ログアウトリポジトリ作成
import {ILogoutRepository} from './ILogoutRepository'
import axios from 'axios';
export class LogoutRepository implements ILogoutRepository{
constructor(){
console.debug('constructor');
}
public logout(userid: string): Promise<void> {
console.debug('LogoutRepository logout start');
return axios.post(`https://hebiblog.org/logout`,{userid})
}
}
リポジトリファクタ作成
コンストラクタにログインリポジトリとログアウトリポジトリのInterfaceを受け取り保持するようにしてます。Interfaceにすることでテスト用のクラスを注入することができます。
import {ILoginRepository} from './ILoginRepository'
import {ILogoutRepository} from './ILogoutRepository'
export class RepositoryFactory {
constructor(loginRepository: ILoginRepository,
logoutRepository: ILogoutRepository,)
{
console.debug('constructor');
// 利用するリポジトリをコンストラクタで受け取り、プロパティに保持する
this.loginRepository = loginRepository
this.logoutRepository = logoutRepository
}
private loginRepository: ILoginRepository
// LoginRepositoryを利用できるようにgetterを作成
get LoginRepository(): ILoginRepository {
return this.loginRepository;
}
private logoutRepository: ILogoutRepository
// LogoutRepositoryを利用できるようにgetterを作成
get LogoutRepository(): ILogoutRepository {
return this.logoutRepository;
}
}
Providerコンポーネント作成
RepositoryProviderコンポーネントを作成します。useRepository関数をimportしたコンポーネントでRepositoryを利用できるようにします。
import {createContext, useContext} from 'react'
import {LoginRepository} from '../repositorys/LoginRepository'
import {LogoutRepository} from '../repositorys/LogoutRepository'
import {RepositoryFactory} from '../repositorys/RepositoryFactory'
export const RepositoryProvider:React.FC = (props) => {
return (
<RepositoryContext.Provider value={new RepositoryFactory(
new LoginRepository(),
new LogoutRepository())}>
{props.children}
</RepositoryContext.Provider>
);
}
const RepositoryContext = createContext({} as RepositoryFactory);
export const useRepository = () => {
return useContext(RepositoryContext);
}
Loginコンポーネント作成
Loginコンポーネントを作成します。ここでDIされたリポジトリを使用します。
import styled from 'styled-components'
import {useRepository} from '../providers/RepositoryProvider'
export const Login = () =>{
// リポジトリを取得
const repository = useRepository();
const onLogin = () => {
// ログイン実行
repository.LoginRepository.login("userId", "password")
.then((res) => {
console.debug(res.data);
})
.catch((error) => {
console.debug("Error:", error);
});
}
const onLogout = () => {
// ログアウト実行
void repository.LogoutRepository.logout("userId")
}
return (
<>
Login画面
<span style={{display: 'flex', flexDirection: 'column'}}>
<LogoutButton onClick={onLogout}>Logout</LogoutButton>
<span style={{display: 'inline-block'}}>
<LoginTextBox type={'text'}/>
<LoginButton onClick={onLogin} style={{ marginLeft: '10px' }}>Login</LoginButton>
</span>
</span>
</>
)
}
const LogoutButton = styled.button`
width: 100px;
`;
const LoginButton = styled.button`
width: 100px;
`;
const LoginTextBox = styled.input`
width: 300px;
`;
全てのコンポーネントで利用可能にする
作成したRepositoryProviderを親コンポーネントにすることで、子コンポーネントでリポジトリが利用できます。以下の実装の場合、Loginコンポーネントでリポジトリを取得して利用できます。
import React from 'react';
import {Login} from './compornents/Login'
import {RepositoryProvider} from './providers/RepositoryProvider'
function App() {
return (
<RepositoryProvider>
<Login/>
</RepositoryProvider>
);
}
export default App;
最後に
いかがでしたでしょうか。useContextを利用し、DIすることができました。(厳密にはDIぽいことができました。)
これにより、どのコンポーネントでもリポジトリを取り出し利用することができます。
また、以下の記事にて、DIしたリポジトリをモック化してUnitテストを行ってみました。興味ある方は見てみてください。
最後までお読みいただきありがとうございました(^^♪