Reactでui-router

最近Reactのプロジェクトでルーティングにui-routerを使ってみたので使い方などの紹介

---

この記事は[オプトテクノロジーズアドベントカレンダー](https://qiita.com/advent-calendar/2018/opt-technologies)13日目のエントリーです。

12日目は @ovrmrw さんの「hyper gatling rx」です
14日目は @hiroshist さんの「AWS Serverless関連で何か」です

---

# ざっくり紹介

もともとAngularJSのrouterライブラリとして有名なui-routerがReactでも使えるというもの
なので、お作法だったりAPIだったりがAngularJSを彷彿とさせる感じで、あまりReact文化圏に馴染む感じではないけど、機能としては充実しててとても便利に使える


# いいところ

まあなんか普通に便利（雑

- ui-routerがやはりよく出来ていて、その機能を享受できるのがよい
    - guardがreact-routerと違ってjsの世界だけで書ける
    - ページ遷移時にPromiseがresolveされるまで待つとか便利
    - 他にも調べると色々あって機能的に困るってことはあんまりなさそう
- Reactとの繋ぎ込みについてはrouterのインスタンス作って `<UIRouter>`に食わせてあげるだけなので難しいことはない
- 型定義ファイルが含まれている(そもそもライブラリ自体の実装がts)ので、型定義の情報に安心感があるし、exampleも充実しているのでドキュメント読みに行かなくても型定義ファイルだけで相当便利にやれる

# つらめなところ

- 情報が少ない(日本語情報は皆無だし、英語で調べてもAngularJSでの情報を参考にする事が多かった）
- Reduxと組み合わせが地味に困る
    - `ui-router/redux`はあまりにも機能が足りてない（というかメンテされてない）
    - そのため、自前でインテグレーションを書くことになる
- ui-routerでのルーティング定義のことを `State`と称しているので、微妙に紛らわしい
- ReactComponentのpropsへ値をinjectするなど、微妙に型定義に困る動作がある

現在のプロジェクトで使ってみての所感としては、問題なく使えるしui-router固有の問題で困らされたことはあまりなかったので普通に使っていいと思った

# 使い方とか

サンプルリポジトリ: https://github.com/sisisin-sandbox/ui-router-in-react


## 普通にReactで使う

最低限これだけ

[サンプルコード](https://github.com/sisisin-sandbox/ui-router-in-react/blob/d43a4aa376ac64f5210a32dfef70db8265a8ed8d/src/App.tsx)

```typescript
import { pushStateLocationPlugin, UIRouter, UIView } from '@uirouter/react';
import React, { Component } from 'react';
import { Home } from './components/Home';
import { Menu } from './components/Menu';
import { User } from './components/User';

const routerStates = [
  {
    name: 'home',
    url: '/home',
    component: Home,
  },
  {
    name: 'user',
    url: '/users/:userId',
    params: {
      userId: {
        type: 'int',
      },
    },
    component: User,
  },
];

class App extends Component {
  render() {
    return (
      <UIRouter states={routerStates} plugins={[pushStateLocationPlugin]}>
        <>
          <Menu />
          <UIView />
        </>
      </UIRouter>
    );
  }
}

export default App;
```

## パラメータを使う

以下のようにrouterのstateの `url`　プロパティで `:userId`のように指定すると、parameterを取得するAPIを使って値を取れるようになる。
このときのparamsに関する設定を別途 `params`プロパティで指定可能で、例えば `type`を指定すると取得時にその型に変換しておいてくれるなど、便利機能が満載

[サンプルコード](https://github.com/sisisin-sandbox/ui-router-in-react/blob/d43a4aa376ac64f5210a32dfef70db8265a8ed8d/src/App.tsx#L13-L22)

```typescript
const routerStates = [
  {
    name: 'user',
    url: '/users/:userId',
    params: {
      userId: {
        type: 'int',
      },
    },
    component: User,
  },
];
```

## ページ遷移前にPromiseを解決して、その結果をpropsとして扱う

router stateの`resolve`プロパティに解決したいPromise及び、その結果をpropsにどんなプロパティ名でinjectするかを指定してやる

[サンプルコード](https://github.com/sisisin-sandbox/ui-router-in-react/blob/a91861dc53f146b956f44db69d076328d67159cb/src/App.tsx#L18-L28)

```typescript
  {
    name: 'home',
    url: '/home',
    component: Home,
    resolve: [
      {
        token: 'resolvedValue',  // props.resolvedValueでアクセスが可能になる
        resolveFn: () => Promise.resolve('resolve!'),
      },
    ],
  },

```

## Reactの中でrouterのAPIを使う

[サンプルコード](https://github.com/sisisin-sandbox/ui-router-in-react/blob/d43a4aa376ac64f5210a32dfef70db8265a8ed8d/src/components/User.tsx)

statesで指定したComponentのpropsにrouter周りのインスタンスがinjectされているので、それを使う
この例では `パラメータを使う`のstateに対応するコンポーネントとして記載。

```typescript
export const User = ({ transition }: { transition: Transition }) => {
  const { userId } = transition.params();
  return (
    <div>
      user {userId}. typeof userId is {typeof userId}
    </div>
  );
};
```

実際の表示↓

![ss_ 2018-12-14 3.16.52.png](https://qiita-image-store.s3.amazonaws.com/0/54872/4f46e479-0103-beec-bb15-a2d195a57aa7.png)


## storybookと共存する

[サンプルコード](https://github.com/sisisin-sandbox/ui-router-in-react/blob/d43a4aa376ac64f5210a32dfef70db8265a8ed8d/src/stories/index.tsx)

他のrouterライブラリ同様、メモリルーターを使う

```typescript
import { storiesOf } from '@storybook/react';
import { memoryLocationPlugin, ReactStateDeclaration, UIRouter } from '@uirouter/react';
import React from 'react';
import { Menu } from '../components/Menu';

const plugins = [memoryLocationPlugin];
const states: ReactStateDeclaration[] = [];
storiesOf('Menus', module).add('Menu', () => {
  return (
    <UIRouter states={states} plugins={plugins}>
      <Menu />
    </UIRouter>
  );
});

```

## Reduxと合わせて使う

Reduxと組み合わせて使うときも大体そのまんまでよいが、一部ハマるので注意

- routerのシングルトンを自前でインスタンス化するときは `UIRouter` コンポーネントに `plugins` や `states`を食わせず、routerインスタンスに自分で設定して上げる必要がある

[サンプルコード](https://github.com/sisisin-sandbox/ui-router-in-react/blob/df949a4960a79853e8d7d43f7d6d4df1f70a4d60/src/App.tsx)

```typescript:App.tsx

export const router = new UIRouterReact();  // routerのインスタンスを使いたいので、自前でnewする
const routerStates = [
  {
    name: 'home',
    url: '/home',
    component: Home,
  },
  {
    name: 'user',
    url: '/users/:userId',
    params: {
      userId: {
        type: 'int',
      },
    },
    component: UserConatiner,
  },
];

// 自前でrouterをnewして使う場合はpluginやstatesは事前に解決した上で<UIRouter>コンポーネントにrouterだけを渡すようにしないと実行時エラーになる
router.plugin(servicesPlugin);
router.plugin(pushStateLocationPlugin);
routerStates.forEach(s => router.stateRegistry.register(s));

export const store = createStore(
  combineReducers({
    users: userReducer,
  }),
);

export type AppState = typeof store extends Store<infer S, any> ? S : never;

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <UIRouter router={router}>
          <>
            <Menu />
            <UIView />
          </>
        </UIRouter>
      </Provider>
    );
  }
}

export default App;
```

## その他の実装パターン

[ui-router公式で提供しているサンプルリポジトリ](https://github.com/ui-router/sample-app-react) があるので、これを見ると大体ある（というか自分が頑張って書いてもこのリポジトリ以上のことはそんなに書けない。。）

# まとめ

というわけでui-router使い方紹介でした。
色々出来て便利なのでみんな使おう。

# 参考資料リンク

- [ui-router/reactのリポジトリ](https://github.com/ui-router/react)
- [公式doc](https://ui-router.github.io/react/)
- [ui-router公式で提供しているサンプルリポジトリ](https://github.com/ui-router/sample-app-react)
- [ui-router/reduxのリポジトリ](https://github.com/ui-router/redux)