初めてVue3とNuxt3を触ってアプリを開発したのでアプトプットとして残します。
<script setup>
setupの必要性
・コンパイラ処理が事前にされるため、パフォーマンスが早くなる
・export default や setup()などのコードを書かなくても良くなる
・setupがないとエラーになる
template
・templateの中の書き方はReactと同じで
NuxtPage
・リロードは実際にされずに必要な部分のみ書き換えられる
・ルーティングをすることができる(app.vueで記述)
・PrivateLinkとは?違いとは?
画面遷移ができる
<NuxtLink to="/price">Price</NuxtLink>
・ルーティングで正しく動作しないことが稀にある
nuxt.config.ts
// この記述がないと、正しいルーティング動作がされない場合がある
app: {
baseURL: '/', // ベース URL を設定
},
動的ルーティング
・ファイル名は下記のように記述する
[id].vue
idの取り出し方は下記のように記述する
[user_id]の場合は{{$route.params.user_id}}とつけた名前によって変わる
{{$route.params.id}}
パラメータ値のバリデーション
パラメーター値によって取得するページでバリデーション処理をする
ミドルウェアでするべきどうかは要確認が必要!
<script setup>
//ルーティングパラメータ値のidをバリデーションする
// エラーの場合は404ページを表示
definePageMeta({
validate:async(route) => {
return /^\d+$/.test(route.params.id)
}
})
</script>
[id]page.vue 左のように書くのはダメみたい->規則を把握
レイアウト
・NuxtLayoutの基本的な仕組み
・layoutsディレクトリに保存され、layouts.default.vueに保存される
カスタムレイアウトの場合はlayouts内に追加する
app.vue
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
layouts/default.vue
<template>
<div>
<div class="container">
<slot></slot>
<footer>
© proglus.jp
</footer>
</div>
</div>
</template>
<style scoped>
.container {
max-width: 800px;
margin: 0 auto;
padding: 0 20px;
color: red;
}
footer {
background-color: orange;
color: white;
text-align: center;
}
</style>
・NuxtLayoutはスロット(slot)を使用して、コンテンツをレイアウトに埋め込むことができる
ブラウザのヘッダーに指定する
app.vueにuseHead()で指定できる
useHead({
title: '学習進捗管理',
link:
meta: [
{
name: 'description',
content: 'Nuxt.js is a framework for creating Vue.js applications.'
}
]
})
linkを追加するとエラーになったので要確認
templateでfor文
<ul>
<li v-for="user in users" :key="user.id">
{{ user.id }}, {{ user.name }}
</li>
</ul>
非同期処理
useFetchで簡単に行える(デフォルトGET)
const {data:users} = await useFetch('https://jsonplaceholder.typicode.com/users')
例外を出す
const {data:users, error} = await useFetch('https://jsonplaceholder.typicode.com/userss');
console.log(error.value)
if (error.value) {
throw createError({statusCode: 404, statusMessage: 'APIエラーが発生しました'});
}
画像を表示(assets)
<img src="~/assets/cat.jpg" alt="猫の画像">
生的ファイルを検索エンジンに見られないようにする方法
全ての検索エンジンに対してサイト全体をクロールを拒否する設定をrobots.txt記述
public/robots.txt (デフォルトでファイルは作成されるがコードは自分で記述が必要)
User-agent: *
Disallow: /
状態管理
// 初期値にtitleを設定 (初期値がないとラインダムな値が設定される)')
const title= useState('title', () => '学習進捗管理')
composables(グローバル変数)
auto-importが行われるディレクトリでcomposablesディレクトリ配下に設定されたファイルが自動的にインポートされる
composables/index.ts
export const useTitle = () => useState('title', ()=> '学習進捗管理アプリ')
呼び出しもと
const title = useTitle()
Vue3勉強
ディレクティブ
v-bind
v-bindを使うと状態の変化によってHTML属性を変えることができる(動的に属性を変えることが可能)
vueでは、aタグにurlを変数から記載するには、普通の書き方では遷移することはできずにv-bindを使用する必要がある
<a v-bind:href="url">詳細</a>
また省略することも可能
<a :href="url">詳細</a>
v-on
イベントによって関数を呼ぶことができる
(発火処理)
<button v-on:click="buy">買う</button>
短縮系
<button @click="buy">買う</button>
リアクティブな値
templateが再度読み込まれるのは、コンポーネントが再描画されるときです。(リアクティブな値が変わった時)
そのため、リアクティブな値を使用しないと再描画されない
ref
単一的な値(stringやnumber)
import {ref} from 'vue';
初期化
const food1 = ref('');
// 最代入
food1.value = event.target.value;
reactive
複数の値を同時に行いたい(オブジェクトや配列)
import {ref, reactive} from 'vue';
const item1 = reactive({
food: 'ラーメン',
price: 400,
})
item1.food = event.target.value;
v-model
:valueの短縮系で
v-on:inputも必要なくなる
双方向のデータバインディングを行い
v-modelで定義された変数と、Vueインスタンスのデータプロパティの変数の紐付けを行い、どちらかが変わるともう一方も変わる挙動をする
一日悩ましたこと
クライアントサイドレンダリングとサーバーサイドレンダリングの違い
下記の二つのAPi通信があった場合にリロード後に1の方は正しい挙動だが、2の方が
通信が行われなかった。
1 const {data:users, error} = await useFetch('https://jsonplaceholder.typicode.com/users');
2 const { data: studytimeData, error: studytimeError } = await useFetch('http://localhost/api/studytime/fetch');
原因はnuxtの設定ファイル(nuxt.config.ts)だと思ったが、違っていて
クライアントサイドレンダリングが原因だとわかった。
下記のように設定すると正しい挙動をした
if (process.client) {
const { data: studytimeData, error: studytimeError } = await useFetch('http://localhost/api/studytime/fetch');
}
理由はnuxtではリロード後に最初にSSRが実行されて、その後にCSRが実行される。
今回の場合、1はSSRとCSR両方で成功するが、2の場合はlocalhostのため、SSRは自身のサーバーを指すため、失敗して、CSRでは成功する。
しかし、SSRで失敗したことがCSRで成功したことに上書きされて、失敗している可能性が高い。
ちなみにSSRの場合はサーバー側での処理のため、ブラウザでデバック結果が表示されないので注意
onMounted
onMounted()はライフライクルのマウント時のCSRで呼ばれる
これは検証済み(ログがターミナルに表示されない)
onMounted(() => {
// クライアントの場合
if (process.client) {
console.log('mounted')
fetchStudyData()
}
// SSRの場合
if (process.server) {
console.log('server')
}
})
Nuxtライフライくる
リロードまたは初回アクセス
|
v
サーバーにリクエスト
|
v
サーバー側でSSRフェーズ開始
┌────────────────────┐
| beforeCreate (サーバー) |
| created (サーバー) |
| beforeMount (サーバー) |
└────────────────────┘
|
v
サーバーがHTMLを生成
|
v
HTMLをブラウザに送信
|
v
ブラウザがHTMLを表示
|
v
クライアント側でJavaScript実行開始
┌────────────────────┐
| beforeCreate (クライアント) |
| created (クライアント) |
| beforeMount (クライアント) |
└────────────────────┘
|
v
ハイドレーション実行
|
v
クライアント側でmounted実行
|
v
ユーザー操作に応じて更新
┌────────────────────┐
| beforeUpdate |
| updated |
| beforeDestroy |
| destroyed |
└────────────────────┘
export default {
async setup() {
// サーバー側とクライアント側両方で実行
console.log('beforeCreate');
// created相当
console.log('created');
// beforeMount相当
onBeforeMount(() => {
console.log('beforeMount');
});
// mounted相当
onMounted(() => {
console.log('mounted');
});
// 更新時のフック
watchEffect(() => {
// データが変更されたときにbeforeUpdateとupdatedが実行されます
});
// 破棄時のフック
onBeforeUnmount(() => {
console.log('beforeDestroy');
});
onUnmounted(() => {
console.log('destroyed');
});
}
}