12
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ReactでuseContextを利用してDIっぽくする

Last updated at Posted at 2019-06-18

はじめに

React Hooks と Context API を利用して Service をどうにかDIっぽく利用してテスト可用性などをあげたいと試行錯誤した結果を簡単な例で紹介します。
(クラス名が適当です)

サービス定義

サービスはクラスの他に Context と useContext する関数を返します。
この際、Context と useContext する関数は {サービス名}Context use{サービス名} のように機械的に名前をつけます。

UserService.tsx
export interface User {
  name: string;
  age: number;
}

export class UserService {
  public async findByName(name: string): Promise<User> {
    const json = await window.fetch(`/api/user/${name}`)
      .then(resp => resp.json());

    return json as User;
  }
}

export const UserServiceContext = createContext(new UserService());

export function useUserService() {
  return useContext(UserServiceContext);
}

サービス利用

サービスを利用する際はuseContextをラップした関数を呼び出します。

User.tsx
export function User(props: { user: string }) {
  const userService = useUserService(props.user);

  const [user, setUser] = useState<User | undefined>(undefined);

  useEffect(() => {
    userService.findByName(props.user)
      .then(u => setUser(u));
  }, [userService, props.user]);

  return (
    <div>
      {user
        ? `${user.name} is ${user.age} year's old.`
        : 'loading...'
      }
    </div>
  );

インジェクション

テストやstorybookなどではServiceをProvideして上書きします。

User.story.tsx
class MockUserService extends UserService {
  public async findByName(name: string):  {
    return { name, age: 42 };
  }
}

storiesOf('user', module)
  .add('User', () => {
    const Wrapped = () => {
      return (
        <UserServiceContext.Provider value={new MockUserService()}>
          <User name="john" />
        </UserServiceContext.Provider>
      );
    };

    return <Wrapped />;
  });

サービス間の依存

多くのケースでは決まったインスタンス渡しておけばどうにかなりそうな気がします。

UserService.tsx
- export const UserServiceContext(new UserService());
+ export const userService = new UserService();
+ export const UserServiceContext = createContext(userService);
OrgService.tsx
import { userService } from '.../UserService.tsx';
:
export const OrgServiceContext = createContext(
  new OrgService(userService),
);

最後に

規模が大きな場合とか複雑なケースではきちんとContextのインターフェースを定義したほうが良いと思いますが、小規模で変更がほぼないのにわざわざ定義するのがめんどうなのです。

おしまい

12
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?