React: Hooks & Component で DI(依存性注入)

React: Hooks & Component で DI(依存性注入)

October 15, 2022

React は DI という考え方が存在しないライブラリとなっています。

DI できなくとも jest のモック関数の機能を使えばテストに困ることもほとんどありません。

そうはいっても DI 可能な Hooks・コンポーネントにしたい場合は、以下のようにすれば実現可能です。

説明 #

以下は DI 可能な Hooks を例として記載しています。Hooks ではなくコンポーネントにしたい場合は、return するのを JSX(React コンポーネント)にしてください。

Props が呼び出し元からもらうプロップス、Deps は依存対象を表現しています。

テスト時においては Deps を差し替えたい一方で、通常利用時にいちいち Deps を引数で渡したくは無いため、デフォルト値を設定して簡潔に利用できるようにしています。

なおコードは TypeScript で記述しています。

Props を受け取りつつ DI する場合 #

type Props = {
  userId: string;
};

type Deps = {
  userRepository: IUserRepository;
};

type Returns = {
  user: IUserEntity | undefined;
};

const defaultDeps: Deps = {
  userRepository: new UserRepository(),
};

export default function useExample(props: Props, deps: Deps = defaultDeps): Returns {
  const { userId } = props;
  const { userRepository } = deps;

  const [user, setUser] = useState<IUserEntity>();

  useEffect(() => {
    (async () => {
      const userEntity = await userRepository.findById(userId);
      setUser(userEntity);
    })().catch(() => {});
  }, [userId, userRepository]);

  return {
    user,
  };
}
// 使用者側の例
const { user } = useExample('123');

Props は無しで DI だけする場合 #

type Props = Record<never, never>;

type Deps = {
  userRepository: IUserRepository;
};

type Returns = {
  users: IUserEntity[];
};

const defaultDeps: Deps = {
  userRepository: new UserRepository(),
};

export default function useExample(_: Props = {}, deps: Deps = defaultDeps): Returns {
  const { userRepository } = deps;

  const [users, setUsers] = useState<IUserEntity[]>([]);

  useEffect(() => {
    (async () => {
      const userEntities = await userRepository.findAll();
      setUsers(userEntities);
    })().catch(() => {});
  }, [userRepository]);

  return {
    users,
  };
}
// 使用者側の例
const { users } = useExample();

Props も DI も無い場合 #

type Props = Record<never, never>;

type Deps = Record<never, never>;

type Returns = {
  count: number;
  increment: () => void;
};

export default function useExample(_: Props = {}, __: Deps = {}): Returns {
  const [count, setCount] = useState(0);

  const increment = (): void => {
    setCount((state) => state + 1);
  };

  return {
    count,
    increment,
  };
}
// 使用者側の例
const { count, increment } = useExample();

最後の例はもはや今回のような Hooks の書き方をする必要もありませんが、DI 可能な Hooks と記述方式を統一したい場合などにご利用ください。