Nuxt.jsにおける大規模アプリケーションでのレイアウトの使い方

こちらはこの記事は[Nuxt.js #2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/nuxt-js-2)の7日目の記事です。
大規模なアプリケーションをNuxt.jsで構築する際のレイアウトの考え方について、自分なりにまとめてみました。

# 前の記事について
本文に入る前に、5日目の @akihiro-iwata さんの記事を紹介します。

[jestを活用したNuxt.jsのコンポーネントのテスト](https://qiita.com/akihiro-iwata/items/baceebd00b6973d67456)

jestを利用したVueコンポーネントのテストに関する記事です。
つい疎かになりがちなUIのテストを記述することは、再利用性や保守性の大きく関わってくるかと思いますので、この記事を参考に自分も取り組んでいきたいです。

(前の記事を紹介するのは、 @akihiro-iwata さんのやり方を真似させていただきました:bow:  素晴らしいです。)

# アプリケーションのレイアウトに関する課題

大規模なアプリケーションのレイアウトを検討していると、以下のような問題に当たることがあります。

- ヘッダーやフッター、ナビゲーションドロワーなどの枠組みはどの画面でも同じ
- ヘッダーやフッターの中身は画面によって変わることがある
  - しかし、ある程度共通の部分もある
  - 共通化できるところはしていきたい

このようなときは枠組みをまず定めて、その枠組みに何を乗せるかを画面毎に決めていくのが一般的かと思います。


# Nuxt.jsのレイアウト機能

Nuxt.jsにはレイアウト機能があり、コンテンツ部分のページ以外の箇所を共通化することができます。

https://ja.nuxtjs.org/guide/views#layouts

レイアウトはデフォルト以外にもカスタムレイアウトを複数定義することができ、ページコンポーネントから切り替えることができます。

しかし、レイアウト機能は以下の制約があります。

- ページコンポーネントから指定できるのはレイアウトの名前だけ
  - レイアウトの中のコンポーネントを切り替えることはできない
- レイアウトはネストすることができない
  - レイアウトを指定できるのはページコンポーネントのみ

この制約の中で最初にあげた問題を解決しようとすると、ある程度割り切りが必要になります。

1. レイアウトを1つだけ用意して、Store等を駆使してヘッダーやフッター等の中身を入れ替える
1. レイアウトを複数用意して、画面毎にレイアウトを記述する

# 具体例で考える

具体的に以下の例で考えてみます。

- 認証があるWEBアプリケーション
- ヘッダーは常に存在する
    - 未ログインの場合はロゴとサインイン、サインアップのボタンを表示する
    - ログイン済みの場合はロゴとユーザ情報、ログアウトボタンを表示する

`nac-hogehoge`というコンポーネントは自作のコンポーネントだと思ってください。
(`nac`はNuxt Advent Calendarの略です。)
また、変数もそれらしいものがあると思ってください。


## 1. レイアウト1つで頑張る

認証状態によってヘッダーを内部で切り替える例です。

**layouts/default.vue**

```vue
<template lang="pug">
#app
  nac-header
    template(v-if="state.authenticated")
      nac-logo
      nac-spacer
      nac-btn(to="/signin") サインイン
      nac-btn(to="/signup") サインアップ
    template(v-else)
      nac-logo
      nac-spacer
      nac-user(:user="state.authUser")
      nac-btn(@click="logout") ログアウト
  nuxt
</template>
```

レイアウトを1つに集約できるので、見るべきファイルははっきりしますが、
条件が増えると管理するのが大変になるのが目に見えています:innocent:

## 2. 完全にレイアウトを分割する

レイアウト同士を完全に分離して、各々実装する例です。

**layouts/default.vue** (ログイン済みのレイアウト)

```vue
<template lang="pug">
#app
  nac-header
    nac-logo
    nac-spacer
    nac-user(:user="state.authUser")
    nac-btn(@click="logout") ログアウト
  nuxt
</template>
```

**layouts/login.vue** (未ログインのレイアウト)

```vue
<template lang="pug">
#app
  nac-header
    nac-logo
    nac-spacer
    nac-btn(to="/signin") サインイン
    nac-btn(to="/signup") サインアップ
  nuxt
</template>
```

一見良さそうに見えますが、ヘッダ以外の部分で変更があったときは全レイアウトの変更が必要になります。
(たとえばルートノードのidを`#app`から`#app-route`に変更したい場合は両方のレイアウトで変更が必要になります。)


# 枠組みだけを扱うコンポーネントを作成する

上記のような問題に対して、アプリケーションの枠組みだけを扱うコンポーネントを作成します。
レイアウトも1つのVueコンポーネントなので、別のコンポーネントを使うことができます。
具体的には、枠組みを`slot`を使って表現していきます。
レイアウトではそのコンポーネントを利用してレイアウトを構築します。

**layouts/base.vue** (枠組みを表現するベースとなるコンポーネント)

```vue
<template lang="pug">
#app
  slot(name="header")
    //- デフォルトのヘッダー
    nac-header
      nac-logo
  slot
  slot(name="footer")
</template>
```

※ 直接使わないように注意すること
　 (使わせないようにするだけなら`components`配下に置けばよいのですが、意味的に近くに置きたかった:stuck_out_tongue:)

デフォルトスロットを使うかどうかは決め次第かと思います。
(アプリケーションのゴールがはっきりしているのであれば使って良いかもしれません)

上記コンポーネントを`base-layout`として読み込んで使った場合は以下のようになります。

**layouts/default.vue** (ログイン済みのレイアウト)

```vue
<template lang="pug">
base-layout
  nac-header(slot="header")
    nac-logo
    nac-spacer
    nac-user(:user="state.authUser")
    nac-btn(@click="logout") ログアウト
  nuxt
  nac-footer(slot="footer")
    span {{ state.message }}
</template>
```

**layouts/login.vue** (未ログインのレイアウト)

```vue
<template lang="pug">
base-layout
  nac-header(slot="header")
    nac-logo
    nac-spacer
    nac-btn(to="/signin") サインイン
    nac-btn(to="/signup") サインアップ
  nuxt
  nac-footer(slot="footer")
    span ログインしてください
</template>
```

このように全体の大まかな配置だけ決めるコンポーネントを作成し、レイアウトコンポーネントでは具体的な中身を入れるようにすると、うまく役割分担ができるかと思います。
新しくヘッダーに表示したいことがあっても、新しいレイアウトコンポーネントを作成するだけで切り替えることができます:blush:

# サンプル

`CodeSandbox`にサンプルを作成しました。
よかったら触ってみてください。

https://codesandbox.io/embed/z2llo9qp6p?module=%2Fpages%2Findex.vue

TypeScript + Pug + Stylus + Vuetify を使って構築しています。


# まとめ

Nuxt.jsのレイアウト機能を使って、アプリケーションのレイアウトを構築する考え方を紹介しました。
ここをおろそかにすると、後々の改修に響いてくるので、しっかり考えていきたいですね。
レイアウトを考えるときの参考になれば幸いです。

(タイトルに大規模って入れたけど、大規模じゃなくても使えそうですね:sunglasses:)

# 次の記事ついて

明日は @hota1024 さんの「Nuxt.js+Laravelで開発しているCMSの紹介」です！