プログラミング

ReactでDIする

hebishima.shogo

はい、hebiです。

Reactで「useContext」を利用し、DIするための記事を書きたいと思います。

スポンサーリンク

DIとは?

DIとは、Dependency Injectionの略で、依存性注入を意味します。
どゆこと?となったかと思いますので、DIありなしのパターンで説明します。

DIなしパターン


DIなしパターンの構成です。

DIなしパターン

Loginコンポーネント内でリポジトリを利用しているため、サーバに依存した構成になっています。
つまり、Unitテストを行う際にもサーバが必要となり、試験に必要なデータをサーバを返してしかテストできないため大変です。

DIありパターン

では、DIを利用した構成を見てみましょう。

DIありパターン

APコンポーネントでリポジトリをDIコンテナに設定し、LoginコンポーネントはDIコンテナにあるリポジトリを利用するイメージです。

DIすることで、テスト用のリポジトリに置き換えることが容易にでき、テストデータを返せます。

DIあり(テスト)

ReactでDI

以下DI構成を作成してみます。Reactの場合、グレー部分の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テストを行ってみました。興味ある方は見てみてください。

あわせて読みたい
ReactでUnitテストを行ってみた
ReactでUnitテストを行ってみた

最後までお読みいただきありがとうございました(^^♪

スポンサーリンク
ABOUT ME
hebi
hebi
エンジニア
フルスタックエンジニアとして活躍中。
HTML5プロフェッショナル認定Level1、Level2所持者です。

未経験の方でも簡単にプログラミングを学べるようにと情報を発信しております。
記事URLをコピーしました