はじめに
自分はNext.jsは多少知っていてもNuxt.jsのことは全く知らないまま、「必要になった時頑張れば何とかなる」と思ってずっとNuxt.jsのキャッチアップをスルーしてきました。
いざNuxt.jsが必要な場面に出くわしてみると、「やれば何とかなる」ではなくて「Next.jsとだいぶ違うやん・・・」と思い想像以上にキャッチアップに苦心しました。
(私のスキルのせいもありますが。。。)
その経験を踏まえ、備えあれば憂いなしではないですがNext.jsの使い手の方がNuxt.jsのことを主要項目だけでも知っておくための参考になればと思い記事にしました。
※Next.jsとReact、Nuxt.jsとVueの区別はしておりませんのでご容赦ください。
読んで欲しい人
- Next.jsの知見はある
- Nuxt.jsは全くわからない
- Next.jsの知見をベースに効率よく Nuxt.jsの基礎を理解したい
コンポーネントシステム
Nuxt.jsにはNext.jsでは当たり前のJSXという概念はなく、 HTMLテンプレート、CSS、JSスクリプトが機能が分かれています。
Next.jsとWeb制作などの静的サイトとの中間、あるいは静的サイトの拡張版みたいなイメージでしょうか。
Next.js
Next.jsのコンポーネントはReactのJSXを基に構築され、HTMLはJSX内のJavaScriptのロジックに組み込まれています。CSSはTailwindなどのライブラリのほか、別のCSSファイルに書かれたCSSモジュールを用いる方法などがあります。
CSSモジュールを用いた実装例
import styles from './MyComponent.module.css';
const MyComponent = () => {
return (
<div className={styles.container}>
<h1>Hello, Next.js!</h1>
</div>
);
};
export default MyComponent;
.container {
padding: 20px;
background-color: #f0f0f0;
}
Nuxt.js
Nuxt.jsはHTMLテンプレート、JSスクリプト、CSSの機能が分かれていますが、シングルファイルコンポーネントとして一つのファイルにまとめることが一般的です。
これにより、コンポーネントの構造がわかりやすくなります。
シングルファイルコンポーネントの実装例
<template>
<div class="container">
<h1>Hello, Nuxt.js!</h1>
</div>
</template>
<script>
export default {
name: 'MyComponent'
};
</script>
<style scoped>
.container {
padding: 20px;
background-color: #f0f0f0;
}
</style>
ライフサイクル
Next.jsのuseEffectでお馴染みのライフサイクルについて、Nuxt.jsでの違いについて説明します。
Next.js
Next.jsでは、useEffect フックを使用してコンポーネントのライフサイクルに沿った副作用を実行します。
第二引数の依存配列により、副作用の実行タイミングを制御します。
(空配列はマウント時のみ、特定の値を含むとその値の変更時のみ実行。)
useEffectフックによるライフサイクルの実装例(マウント時のみ発動)
import{ useEffect, useState } from 'react';
const MyComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
// マウント時の副作用
const fetchData = async () => {
const response = await fetch('your-api-url');
const result = await response.json();
setData(result);
};
fetchData();
return () => {
// クリーンアップ処理
};
}, []); // 空配列によりマウント時のみ実行
return <div>{data}</div>;
};
Nuxt.js
Nuxt.jsではコンポーネントのライフサイクルをonMountedとwatchを使って実装します。
onMounted: コンポーネントがマウントされたときに実行されます。
watch: 特定の依存データを監視し、そのデータが変更されたタイミングで関数を実行します。
Next.jsの useEffect は、複数のライフサイクルイベントを一つのフックでカバーする一方で、Nuxt.jsでは機能ごとに特化したフックに分割されているという違いがあります。
onMountedとwatchによるライフサイクルの実装例
<template>
<div>{{ data }}</div>
</template>
<script>
import { onMounted, ref, watch } from 'vue';
export default {
setup() {
const data = ref(null);
onMounted(async () => {
const response = await fetch('your-api-url');
data.value = await response.json();
});
watch(data, (newValue) => {
// dataが変更されたときの副作用
});
return { data };
}
};
</script>
Next.jsのjsxとNuxt.jsのテンプレート構文(templateタグ)との違い
Next.jsはjsx内にHTMLを埋め込むため、JavaScriptと合わせて実装することが可能です。
一方、Nuxt.jsのテンプレート構文は構文的に正規の HTMLとして取り扱われるため、そのままではJavaScriptを実装することができません。
これを踏まえて実装の差異を説明します。
Next.js
Next.jsでは、JSXを内UIを記述します。JSXはJavaScriptの拡張であり、HTMLに似た構文を持ちますが、JavaScriptをそのまま実装することができます。
jsxによるJavaScriptの実装例
const MyComponent = () => {
const message = "Hello, Next.js!";
return <div>{message}</div>;
};
Nuxt.js
Nuxt.jsのテンプレート構文は構文的に正規の HTMLとして取り扱われるため、ディレクティブを通じてDOMに命令を与えます。
ディレクティブの例
v-bind:属性やプロパティを動的にバインドします。省略形は「:」です。
例:v-bind:href="url"または:href="url"。
v-on:イベントリスナーを追加します。省略形は「@」です。
例:v-on:click="handleClick"または@click="handleClick"。
v-model:フォーム入力とデータを双方向バインドします。
例:v-model="message"。
v-if:条件によって要素をレンダリングします。v-elseやv-else-ifと組み合わせて使用することもできます。
例:v-if="isVisible"。
v-for:配列やオブジェクトを基に要素を繰り返しレンダリングします。
例:v-for="item in items"。
v-show:条件によって要素の表示・非表示を切り替えます。v-ifと異なり、要素は常にDOMに存在し、displayスタイルで制御されます。
例:v-show="isVisible"。
v-slot:スロットの内容を定義します。省略形は#です。
例:v-slot:headerまたは#header。
ディレクティブを用いたテンプレート構文のJavaScriptの実装例
<template>
<div v-if="isVisible">
{{ message }}
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Nuxt.js!',
isVisible: true
};
}
};
</script>
グローバルステートマネジメント
Next.jsとNuxt.jsのどちらにもグローバルステートはあります。
フレームワークの違いと言うよりライブラリの違いの方が大きいかもしれませんが、自分が日頃使っているRecoilとVuexを用いて比較をしたいと思います。
Next.js
RecoilはNext.js用のステート管理ライブラリで、アプリケーション全体の状態をアトム(状態の最小単位)とセレクタ(派生状態)を通じて管理します。
アプリケーションのルート(_app.jsxなど)で を使ってラップする必要があります。
アトムは個別に定義され、必要なコンポーネントでインポートして再利用できます。
Recoilによるグローバルステートの実装例
import { useRecoilState } from 'recoil';
import { textState } from '../state/textState'; // アトムのインポート
const MyComponent = () => {
const [text, setText] = useRecoilState(textState);
return (
<div>
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
<p>{text}</p>
</div>
);
};
export default MyComponent;
import { atom } from 'recoil';
export const textState = atom({
key: 'textState', // ユニークなキー
default: '', // 初期値
});
Nuxt.js
VuexはNuxt.js(Vue)の公式ステート管理ライブラリで、単一のストア内にアプリケーション全体の状態を一元管理します。
(単一のストアなので例としてはRecoilよりReduxの方が近そうですが、Recoilの方が使い慣れているのでそちらを使ってしまいました。。。)
Nuxt.jsプロジェクトでは、store ディレクトリ内にVuexストアを定義します。
ストアは自動的にNuxt.jsに統合され流ので、特別なラッピングは不要です。
ストアは状態(state)、ミューテーション(mutations)、アクション(actions)、ゲッター(getters)で構成されます。
Vuexによるグローバルステートの実装例
<template>
<div>
<input type="text" v-model="text" />
<p>{{ text }}</p>
</div>
</template>
<script>
export default {
computed: {
text: {
get() {
return this.$store.state.text;
},
set(value) {
this.$store.commit('setText', value);
}
}
}
};
</script>
export const state = () => ({
text: ''
});
export const mutations = {
setText(state, text) {
state.text = text;
}
};
ステート(props)の渡し方
グローバルステートではなく直接propsとしてステートを渡す場合の違いについて説明します。
Next.js
Next.jsでは、親コンポーネントから子コンポーネントへのデータ渡しはプロパティ(props)を通じて行われます。複数のプロパティがある場合、スプレッド構文を使用して一度に子コンポーネントに渡すことができます。
propsを使ったステートの渡し方の実装例
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const data = { message: 'Hello from parent', number: 1217 };
return <ChildComponent {...data} />;
};
const ChildComponent = ({ message, number }) => {
return (
<div>
<p>{message}</p>
<p>{number}</p>
</div>
);
};
Nuxt.js
Nuxt.js(Vue)では、v-bindを使用して、ステートやストアを子コンポーネントに渡すことができます。 コンポーネントと合わせて使用することで、ネストされたページコンポーネントに渡すことができます。
v-bindとNuxtChildを使ったネストされたページコンポーネントへのステートの渡し方の実装例
<template>
<NuxtChild v-bind="{ message: 'Hello from parent', number: 1217 }" />
</template>
<template>
<div>
<p>{{ message }}</p>
<p>{{ number }}</p>
</div>
</template>
<script>
export default {
props: {
message: String,
number: Number
}
};
</script>
子コンポーネントへのコンポジション(children)を使った要素の渡し方
Next.jsでよく使われるchildrenを使った子コンポーネント要素の渡し方については、Nuxt.jsの方が柔軟だなーという印象です。
Next.js
Next.jsのchildrenはコンポーネントに渡される特別なプロパティで、children を使用して親コンポーネントから子コンポーネントへ任意の JSX やコンテンツを渡すことができます。
ただしchildren は、コンポーネント内の単一かつ特定の場所にのみ挿入され、その位置は親コンポーネントによって決定されます。
childrenを使った要素の渡し方の実装例
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
return (
<ChildComponent>
<p>Hello, this is a child content!</p>
</ChildComponent>
);
};
const ChildComponent = ({ children }) => {
return <div>{children}</div>; // childrenは単一かつ特定の場所にのみ挿入される
};
Nuxt.js
Nuxt.jsでは、名前付きスロットを使用してNext.jsのchildrenより柔軟なコンテンツ挿入が可能です。
親コンポーネントは特定の名前を持つ複数のスロットを提供し、子コンポーネントはその名前に基づいて異なる場所にコンテンツを挿入できます。
名前付きスロットを用いた要素の渡し方の実装例
<template>
<ChildComponent>
<template v-slot:header>
<h1>Header Content</h1>
</template>
<template v-slot:default>
<p>Default Content</p>
</template>
<template v-slot:footer>
<p>Footer Content</p>
</template>
</ChildComponent>
</template>
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- default slot -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
まとめ
自分はキャッチアップに時間を要しましたが、(ざっくりですが)同じJSのフロントエンド系フレームワークなので相違点の要所さえ理解ができれば効率よくキャッチアップができるのではないかと思います。
まだここに書ききれない項目もありますので、随時内容は見直していきたいなーと思っています。
記載の誤りなど何かありましたら、ご指導いただけますと幸いです。