【Next.js】Linkコンポーネント と useRouter を丁寧に解説してみた

おはこんばんちは、ちーずです。
記念すべき10日目の記事は、「LinkコンポーネントとuseRouter」に関してです！


## `next/router`
`next/router`は、routerオブジェクトにアクセスするためのパッケージです。

`useRouter` と `withRouter`(とそれらの型)をexportしていますが、

* 関数Component → useRouter
* クラスComponent → withRouter

と覚えておけばOKです。
両方とも、routerオブジェクトが返されます。

### routerオブジェクト

基本的にはNext.jsに最適化された`window.location`や`window.history`みたいなもので、
url関連の情報を取得・操作するための処理が含まれています。


下記のような構造のrouterオブジェクトの例をみてみましょう。

| 例 |  | 
|:-----------|:------------|
| (next.config.jsで設定した) basePath       | /tech        |
| ページComponentのディレクトリ     |  /pages/blogs/[code].ts      |
| ページURL       | `https://example.com/tech/blogs/nextjs?page=3`        |

このようなケースのrouterオブジェクトの値はこのようになります。(※よく使うもののみ)

```jsonnet
pathname : '/blogs/[code]',                  // page配下のディレクトリ
query    : { code: 'nextjs', page: '3' },    // 動的ルーティングのパラメータと、クエリパラメータ のオブジェクト
basePath : '/tech',                          // basePath
asPath   : '/blogs/nextjs?page=3'            // basePathを以降のURL
```

※ ハッシュを取得したい場合、`useHash`に`asPath`を渡してあげましょう。

https://github.com/streamich/react-use/blob/master/docs/useHash.md


次によくつかうメソッドを紹介します。

#### router.push
クライアント側の遷移処理をおこないます。
 
基本的な書き方は、`router.push(url, as, options);`です。

```router.pushの型.ts
push(url: Url, as?: Url, options?: TransitionOptions): Promise<boolean>;
```
asは基本undefinedで良いです。(9.5.3前のデコレーター)
また、optionsの型は下記になります。

```ts
interface TransitionOptions {
  shallow?: boolean;        // 浅いルーティングにするか (デフォは false)
  locale?: string | false;  // 新しいページのロケール設定
  scroll?: boolean;         // 遷移後に先頭にスクロールするか (デフォはtrue)
}
```

:::note info
shallowルーティングとは、 getStaticProps や getServerSideProps を実行せずにURLの変更することです。
:::

また、URLオブジェクトを引数に渡すこともできます。

```ts
router.push({
  pathname: '/blog',
  query: { code: 'nextjs' }
});
```


#### router.replace
履歴に追加せずに、遷移処理をおこないます。
型は`router.push`と全く一緒です。

```js
Router.replace('/blog');
```


#### router.back
履歴を１つ戻します。(ブラウザバック、戻るボタンクリックと同じ挙動)
`window.history.back()`と同様。

#### router.reload
リロード(更新)します。
`window.location.reload()`と同様。

#### router.events
URLやハッシュの変更を検知して、処理を実行できます。

* `beforeHistoryChange` : ブラウザの履歴を変更する直前
* `routeChangeComplete` : ルートが完全に変更され終わったとき
* `hashChangeComplete`  : ハッシュの変更が完了した時

などがあります。


https://nextjs-ja-translation-docs.vercel.app/docs/api-reference/next/router#routerevents

## `next/link`
Linkコンポーネントを返すパッケージです。

```tsx
import Link, { LinkProps } from 'next/link';

export const Navigation = () => (
  <Link href="/">
    <a>トップに戻る</a>
  </Link>
)
```

Linkコンポーネントには下記のPropsがあります。

```tsx
export declare type LinkProps = {
  href: Url;               // URL もしくは  pagesディレクトリ内のパス
  as?: Url;                // ブラウザのURLバーに表示されるパス (動的ルーティング用)
  replace?: boolean;       // 履歴に追加せずに、遷移処理をするか (デフォ false)
  scroll?: boolean;        // 遷移後に先頭にスクロールするか (デフォはtrue)
  shallow?: boolean;       // 浅いルーティングにするか (デフォは false)
  passHref?: boolean;      // 子にhrefプロパティを送るか (デフォは false)
  prefetch?: boolean;      // バックグラウンドでページをプリフェッチするか  (デフォは true)
  locale?: string | false; // 新しいページのロケール設定
};
```

### 動的ルーティングの際の記述方法
先ほど使ったこのページに遷移するLinkコンポーネントを作成したい場合

| 例 |  | 
|:-----------|:------------|
| ページComponentのディレクトリ     |  /pages/blogs/[code].ts      |
| ページURL       | `https://example.com/tech/blogs/nextjs      |

2パターンの書き方があります。

```tsx
// 1. hrefにpathnameを渡す方法
const Navigation1 = () => (
  <Link href='/blogs/[code]' as='/blogs/nextjs' passHref>
    <a>Hi! Next.js</a>
  </Link>
)

// 2. hrefにURLオブジェクトを渡す方法
const Navigation1 = () => (
  <Link href={{ pathname: '/blogs', query: { code: 'nextjs' }}} passHref>
    <a>Hi! Next.js</a>
  </Link>
)
```

### `passHref`とは？
`passHref`は、子要素のaタグにhrefを渡すプロパティです。
基本的に、子要素がaタグの時は必ず設定しましょう。
(設定しないと、空のaタグになっていまい、SEOに悪影響を及ぼす可能性がございます。)


```tsx
const Navigation3 = () => (
  <Link href='/blogs/[code]' as='/blogs/nextjs' passHref>
    <a>Hi! Next.js</a>
  </Link>
)

// 出力すると、 <a href="/blogs/nextjs">Hi! Next.js</a>  みたいになる
```

:::note warn
<Link>直下がコンポーネントだった場合、そのComponentがpassHrefを受け取れるようforwardRefにラップしましょう。
:::


### 子要素が`<a>`以外の時
そのような時は、`onClick`でユーザーの遷移処理 (処理でいうと router.push) を実行します。
href属性はなくなってしまうため、HTMLの構造上リンクであると伝えたい時は、
なるべく`<Link>`の直下は aタグ もしくは 一番親要素がaタグのコンポーネント を使いましょう。


---

以上、`<Link>` と `useRouter`についてでした！