\n\u003C/template>",{"id":293,"title":294,"titles":295,"content":296,"level":214},"/ja/vue/components#コンポーネント間のデータの受け渡し","コンポーネント間のデータの受け渡し",[50],"Vue コンポーネント間でデータをやり取りする基本的な方法として、props と emit を使用します。",{"id":298,"title":299,"titles":300,"content":301,"level":302},"/ja/vue/components#props","Props",[50,294],"親コンポーネントから子コンポーネントにデータを渡すための方法です。 まずは子コンポーネント側で defineProps マクロを使用し、受け取りたいデータを定義します。 \u003C!-- Child.vue -->\n\u003Cscript setup lang=\"ts\">\ndefineProps\u003C{ message: string }>()\n\u003C/script> 次に親コンポーネント側で、子コンポーネントにデータを渡すために v-bind ディレクティブを使用します。:props名=\"データ\" という形式で、子コンポーネントにデータを渡すことができます。 \u003C!-- Parent.vue -->\n\u003Ctemplate>\n \u003CChild :message=\"message\" />\n\u003C/template> また、props 名とデータの変数名が同名の場合は省略記法を使うことができます。 \u003C!-- Parent.vue -->\n\u003Ctemplate>\n \u003CChild :message />\n\u003C/template>",3,{"id":304,"title":305,"titles":306,"content":307,"level":302},"/ja/vue/components#emit","Emit",[50,294],"子コンポーネントから親コンポーネントにイベントを発火するための方法です。 まずは子コンポーネント側で defineEmits マクロを使用し、発火したいイベントを定義します。\nemit 関数を用いて、イベントを発火することができます。 \u003C!-- Child.vue -->\n\u003Cscript setup lang=\"ts\">\nconst emit = defineEmits\u003C{ sendMessage: [] }>()\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cbutton type=\"button\" @click=\"emit('sendMessage')\">\n Click me\n \u003C/button>\n\u003C/template> 発火されたイベントは親コンポーネント側で v-on ディレクティブを使用して受け取ることができます。 \u003C!-- Parent.vue -->\n\u003Cscript setup lang=\"ts\">\nfunction handleSendMessage() {\n console.log('Message sent!')\n}\n\u003C/script>\n\n\u003Ctemplate>\n \u003CChild @send-message=\"handleSendMessage\" />\n\u003C/template> 以下のように、イベント発火時に子コンポーネントからデータを受け渡すこともできます。 \u003C!-- Child.vue -->\n\u003Cscript setup lang=\"ts\">\nconst emit = defineEmits\u003C{ sendMessage: [string] }>()\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cbutton type=\"button\" @click=\"emit('sendMessage', 'Hello, Vue!')\">\n Click me\n \u003C/button>\n\u003C/template> \u003C!-- Parent.vue -->\n\u003Cscript setup lang=\"ts\">\nfunction handleSendMessage(message: string) {\n console.log(message)\n}\n\u003C/script>\n\n\u003Ctemplate>\n \u003CChild @send-message=\"handleSendMessage\" />\n\u003C/template> それぞれの詳しい API ドキュメントから確認することができます。",{"id":309,"title":246,"titles":310,"content":311,"level":214},"/ja/vue/components#チャレンジ",[50],"右のプレイグラウンドでは、props と emit を使ってコンポーネント間のデータの受け渡しを行っています。\nVue.js では スロット という機能を利用することで、親コンポーネントからコンポーネントにテンプレートを挿入することができます。\n右のプレイグラウンドを編集して、スロットを使ったテンプレートの挿入を行ってみましょう。 子コンポーネント (Child.vue) でスロットの定義を行うdefineSlot マクロ を使うことにより、型安全なスロットを定義することができます。\n定義ができたら、template 内で slot タグを配置することで渡されたテンプレートの挿入を行うことができます。\u003Cscript setup lang=\"ts\">\ndefineSlots\u003C{ paragraph: () => any }>()\n\u003C/script>\n\n\u003Ctemplate>\n \u003Ch2>Child Component\u003C/h2>\n \u003Cp>\u003Cslot name=\"paragraph\" />\u003C/p>\n\u003C/template>\n親コンポーネント (app.vue) で slot にテンプレートを挿入する\n親コンポーネント側で、子コンポーネントにテンプレートを挿入するために v-slot ディレクティブを使用します。\n(ここでは v-slot の省略記法の # を使用しています)\u003Ctemplate>\n \u003CChild>\n \u003Ctemplate #paragraph>\n Hello from \u003Cspan class=\"red--text\">Parent!\u003C/span>\n \u003C/template>\n \u003C/Child>\n\u003C/template> html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .sHLBJ, html code.shiki .sHLBJ{--shiki-default:#998418;--shiki-dark:#B8A965}html pre.shiki code .sfsYZ, html code.shiki .sfsYZ{--shiki-default:#A65E2B;--shiki-dark:#C99076}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .sbEgt, html code.shiki .sbEgt{--shiki-default:#999999;--shiki-default-font-style:italic;--shiki-dark:#666666;--shiki-dark-font-style:italic}",{"id":58,"title":57,"titles":313,"content":314,"level":204},[],"おめでとうございます!これで Vue の基本を学び終わりました。さらに Vue について学びたい場合は、公式のVueドキュメントをご覧ください。より詳しいガイドやチュートリアルが用意されています。",{"id":316,"title":57,"titles":317,"content":318,"level":204},"/ja/vue/summary#vueの基本のまとめ",[],"おめでとうございます!これで Vue の基本を学び終わりました。さらに Vue について学びたい場合は、公式のVueドキュメントをご覧ください。より詳しいガイドやチュートリアルが用意されています。 次は、Nuxt がどのようにあなたの開発体験をさらに向上させるかを見ていきましょう。",{"id":65,"title":64,"titles":320,"content":321,"level":204},[],"この章では、Nuxt のコアコンセプトについて説明します。",{"id":323,"title":64,"titles":324,"content":325,"level":204},"/ja/concepts#nuxt-のコンセプト",[],"この章では、Nuxt のコアコンセプトについて説明します。 Nuxt は、直感的で拡張可能な方法で、タイプセーフで高性能で本番向けのフルスタック Web アプリケーションやウェブサイトを Vue.js で作成するための無料かつオープンソースのフレームワークです。Vue.js に慣れていない場合は、まず Vue の基本セクションから始め、最初に 公式 Vue ドキュメント を読むことをお勧めします。",{"id":327,"title":328,"titles":329,"content":330,"level":214},"/ja/concepts#自動化と慣習","自動化と慣習",[64],"Nuxt は慣習と意見のあるディレクトリ構造を使用して、繰り返しのタスクを自動化し、開発者が機能の追加に集中できるようにします。設定ファイルはカスタマイズ可能で、デフォルトの動作を上書きすることができます。 ファイルベースのルーティング: pages/ ディレクトリの構造に基づいてルートを定義します。これにより、アプリケーションを整理し、手動でルートを構成する必要がなくなります。自動インポート: 対応するディレクトリに Vue コンポーザブルやコンポーネントを記述し、ツリーシェイキングと最適化された JS バンドルの利点を活用してインポートすることができます。コード分割: Nuxt は自動的にコードを小さなチャンクに分割し、アプリケーションの初期読み込み時間を短縮するのに役立ちます。サーバーサイドレンダリング: Nuxt には組み込みの SSR 機能が付属しているため、別個のサーバーを設定する必要はありません。データ取得ユーティリティ: Nuxt は、SSR 互換のデータ取得を処理するコンポーザブルや異なる戦略を提供します。ゼロコンフィグ TypeScript サポート: 自動生成された型と tsconfig.json を使用して、TypeScript を学ぶ必要なく、タイプセーフなコードを記述できます。構成済みのビルドツール: 開発時にホットモジュールリプレースメント(HMR)をサポートし、本番用にコードをバンドルするために Vite をデフォルトで使用しています。 Nuxt はこれらを処理し、フロントエンドとバックエンドの機能を提供するため、あなたが重要視すべきことに集中できます: あなたの Web アプリケーションを作成すること。",{"id":332,"title":217,"titles":333,"content":334,"level":214},"/ja/concepts#はじめに",[64],"このプレイグラウンドには、Nuxt がインストールされ、設定されているため、アプリケーションの作成を直接開始できます。自分のマシンに Nuxt をローカルにインストールしたい場合は、インストールガイドに従ってください。 始めるには、Nuxt アプリケーションのエントリがどのように定義されているかについて学びましょう。",{"id":72,"title":71,"titles":336,"content":337,"level":204},[],"Nuxt では app.vue がアプリケーションのエントリーポイントとなっています。\nアプリケーションを制御するために最小限の app.vue を定義して、自分自身のアプリケーションの実装を始めることができます。(任意)\nこの例ではメッセージを画面に出力するだけの単純な実装が行われています。",{"id":339,"title":71,"titles":340,"content":341,"level":204},"/ja/concepts/app-vue#アプリケーションのエントリ",[],"Nuxt では app.vue がアプリケーションのエントリーポイントとなっています。\nアプリケーションを制御するために最小限の app.vue を定義して、自分自身のアプリケーションの実装を始めることができます。(任意)\nこの例ではメッセージを画面に出力するだけの単純な実装が行われています。 次のチャプターで説明する pages/ ディレクトリはオプショナルで、存在しない場合は vue-router の依存関係を含めません。\nこれは、ルーティングが必要ないランディングページやアプリケーションを実装する際に便利です。 また、nuxt.config.ts を通じてアプリケーションの設定を行うことができます。(nuxt.config.js, nuxt.config.mjs も同様にサポートしています)\nデフォルトでは、ほとんどのユースケースをカバーするように構成されており、必要に応じて設定を上書きすることができます。\n詳細なオプションに関しては Nuxt 公式ドキュメント をご覧ください。 Nuxt アプリの機能をさらに拡張するには、ルーティング セクションに進んで、アプリをマルチページにする方法を学びましょう。",{"id":79,"title":78,"titles":343,"content":344,"level":204},[],"",{"id":346,"title":78,"titles":347,"content":344,"level":204},"/ja/concepts/routing#ルーティング",[],{"id":349,"title":350,"titles":351,"content":352,"level":214},"/ja/concepts/routing#ファイルシステムルーター","ファイルシステムルーター",[78],"ファイルシステムルーターは Nuxt の主要機能の 1 つです。pages/ ディレクトリ内の各 Vue ファイルが、それに対応する URL(ルート)を作成し、そのファイルの内容を表示します。\nこの例では、pages/index.vue が / に、pages/foo.vue が /foo に対応しています。\nこのルーティングは vue-router をベースに実装されています。 また、Nuxt は各ページに対してコード分割等の最適化により、リクエストされたルートに対して最小限の JavaScript を配信します。",{"id":354,"title":355,"titles":356,"content":357,"level":214},"/ja/concepts/routing#ナビゲーション","ナビゲーション",[78],"pages/ にルートを作成したら、\u003CNuxtLink> コンポーネントを使うことでナビゲーションを行うことができます。 \u003CNuxtLink> コンポーネントでは to という props にルートを指定することでリンクを作成することができます。\u003CNuxtLink> コンポーネントは自動的に最適化されるため、通常の \u003Ca> 要素と、href 属性を利用したリンクの作成と比べ、高速にナビゲーションを行うことができます。",{"id":359,"title":360,"titles":361,"content":362,"level":214},"/ja/concepts/routing#ルートパラメータ","ルートパラメータ",[78],"/pages ディレクトリでは、ファイル名の一部を [] で囲うことで動的なルーティングを定義することができます。(例: pages/posts/[id].vue) この [] で囲われたルートパラメータは useRoute() を通じてアクセスすることができます。 \u003C!-- pages/posts/[id].vue -->\n\u003Cscript setup lang=\"ts\">\nconst route = useRoute()\n\n// /posts/1 にアクセスした際、route.params.id は 1 になる\nconsole.log(route.params.id)\n\u003C/script>",{"id":364,"title":246,"titles":365,"content":366,"level":214},"/ja/concepts/routing#チャレンジ",[78],"/posts/[id] というルートに対して、 /, /foo からナビゲーション出来るように実装してみましょう。\n/posts/[id] では、ルートパラメータから id を読み取り、その値を画面に表示してみましょう。 そのためには: pages/posts/[id].vue ファイル内で useRoute() を使って params から id を取得し、画面に表示します。pages/index.vue と pages/foo.vue に \u003CNuxtLink> コンポーネントを使って /posts/[id] にナビゲーションするリンクを作成します。\nここでは例として、/posts/1 にナビゲーションするリンクを作成してみましょう。\n(実際には値は任意のもので構いません) もし行き詰まったら、以下のボタンかエディタの右上にあるボタンをクリックして回答を見ることができます。 ここで登場した、useRoute 関数や \u003CNuxtLink> コンポーネント、Nuxt のコアコンセプトの 1 つとなっている Auto Imports によって自動的に import されるため、import 文を記載することなく利用することができます。Auto Imports について次のセクションでより詳しく学びましょう。 html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":86,"title":85,"titles":368,"content":369,"level":204},[],"自動インポートも Nuxt のコアコンセプトの 1 つです。",{"id":371,"title":85,"titles":372,"content":373,"level":204},"/ja/concepts/auto-imports#自動インポート",[],"自動インポートも Nuxt のコアコンセプトの 1 つです。 https://nuxt.com/docs/guide/concepts/auto-imports 自動インポートは明示的にインポートすることなく、コンポーネント、コンポーザブル、および Vue.js の API をアプリケーション全体で使用できるように自動的にインポートする機能です。\n従来のグローバル宣言とは異なり、Nuxt は型情報や IDE の補完、ヒントを保持し、本番コードで使用されているもののみを含めます。 Nuxt のディレクトリ構造の規約のおかげで、 components/、composables/、および utils/ を自動的にインポートすることができます。\nこの例では、components ディレクトリに定義された Counter.vue コンポーネントと、composables ディレクトリに定義された useCounter.ts を明示的なインポートなしで使用しています。app.vue では Counter コンポーネントを使用し、Counter.vue では useCounter() を使用しています。 また、Nuxt はいくつかのコンポーネントやコンポーザブル、ユーティリティも提供しています。\nルーティング のセクションで登場した NuxtLink コンポーネントがその一例です。\n他にも、データフェッチで利用する useFetch() コンポーザブルやランタイムの設定にアクセスする useRuntimeConfig() コンポーザブル、ページナビゲーションのための navigateTo() ユーティリティ関数などがあります。\nたくさんあるので、そのほかのものは Nuxt 公式ドキュメントの Components、Composables、Utils のセクションを参照してください。 また、Nuxt では明示的なインポートもサポートしており、この場合は #imports からインポートすることが可能です。 import { computed, ref } from '#imports'\n\nconst count = ref(1)\nconst double = computed(() => count.value * 2) 自動インポート機能は nuxt.config.ts でオプトアウトすることも可能です。\nこの場合は上記の明示的なインポートが必要になります。 // nuxt.config.ts\nexport default defineNuxtConfig({\n imports: {\n autoImport: false\n }\n})",{"id":375,"title":246,"titles":376,"content":377,"level":214},"/ja/concepts/auto-imports#チャレンジ",[85],"実際に utils/double.ts ファイルに対して、自動インポート可能な関数を実装してみましょう。 関数は任意のもので構いませんが、例として「与えられた数値を二倍にして返す double() 関数」を実装してみましょう。\n関数が実装できたら、app.vue 内の template で使用して、2 倍された数値を画面に表示してみましょう。 html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .sHLBJ, html code.shiki .sHLBJ{--shiki-default:#998418;--shiki-dark:#B8A965}",{"id":93,"title":92,"titles":379,"content":380,"level":204},[],"Nuxt では、特定のルートにナビゲートする前にコードを実行する為のミドルウェアを提供しています。\nこの機能は例えば、認証状態によってページへのアクセスを制限する場合などに便利です。",{"id":382,"title":92,"titles":383,"content":384,"level":204},"/ja/concepts/middleware#ミドルウェア",[],"Nuxt では、特定のルートにナビゲートする前にコードを実行する為のミドルウェアを提供しています。\nこの機能は例えば、認証状態によってページへのアクセスを制限する場合などに便利です。 ミドルウェアにはグローバルミドルウェアとページ単位のミドルウェアの 2 つの種類があります。\nどちらも、middleware ディレクトリに実装します。",{"id":386,"title":387,"titles":388,"content":389,"level":214},"/ja/concepts/middleware#グローバルミドルウェア","グローバルミドルウェア",[92],"グローバルミドルウェアは以下のように定義することができます。 -| middleware/\n ---| hello.global.ts // middleware/hello.global.ts\nexport default defineNuxtRouteMiddleware(() => {\n console.log('hello')\n})",{"id":391,"title":392,"titles":393,"content":394,"level":214},"/ja/concepts/middleware#ページ単位のミドルウェア","ページ単位のミドルウェア",[92],"ページ単位のミドルウェアは以下のように定義することができます。 -| middleware/\n ---| helloA.ts // middleware/hello.ts\nexport default defineNuxtRouteMiddleware(() => {\n console.log('helloA')\n}) \u003C!-- pages/a.vue -->\n\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n middleware: ['hello'],\n})\n\u003C/script>\n\n\u003Ctemplate>\n \u003Ch1>Hello A\u003C/h1>\n\u003C/template>",{"id":396,"title":397,"titles":398,"content":399,"level":214},"/ja/concepts/middleware#ミドルウェアの実行タイミング","ミドルウェアの実行タイミング",[92],"これらのミドルウェアは、クライアント上でのナビゲーション時はもちろん、SSR または SSG でのページ生成時にもサーバーサイドで実行されます。\nミドルウェアでローカルストレージなどのクライアントサイドの API を使用する場合は、クライアントサイドのみで実行されるようにする必要があります。import.meta を使うことで実行している環境を判定することができます。\nサーバーサイドでの実行をスキップする場合は import.meta.server を利用します。 export default defineNuxtRouteMiddleware((to) => {\n // skip middleware on server (if (import.meta.client) { ... } でも同様)\n if (import.meta.server)\n return\n\n // some processing\n window.localStorage.setItem('key', 'value')\n})",{"id":401,"title":246,"titles":402,"content":403,"level":214},"/ja/concepts/middleware#チャレンジ",[92],"localStorage の情報を読み取って、特定の値がある場合のみ /foo にアクセス可能にするミドルウェアを作成してみましょう。\n今回は例として、isSignedIn というキーに true が設定されている場合のみ /foo にアクセス可能にするミドルウェアを作成します。\n値のセットは index.vue から行えるようにボタンを設置してみましょう。 html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHLBJ, html code.shiki .sHLBJ{--shiki-default:#998418;--shiki-dark:#B8A965}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}",{"id":100,"title":99,"titles":405,"content":406,"level":204},[],"Nuxt は UI パターンを再利用可能にするための機能を提供しています。\nlayout は ~/layouts デイレクトリに実装され、app.vue で NuxtLayout を使用することで適応されます。\nlayout は各ページごとに definePageMeta を通して選択することができます。",{"id":408,"title":99,"titles":409,"content":410,"level":204},"/ja/concepts/layout#レイアウト",[],"Nuxt は UI パターンを再利用可能にするための機能を提供しています。\nlayout は ~/layouts デイレクトリに実装され、app.vue で NuxtLayout を使用することで適応されます。\nlayout は各ページごとに definePageMeta を通して選択することができます。 -| layouts/\n ---| default.vue\n ---| custom.vue \u003C!-- layouts/custom.vue -->\n\u003Ctemplate>\n \u003Cdiv>\n \u003Cp>Some default layout content shared across all pages\u003C/p>\n \u003Cslot />\n \u003C/div>\n\u003C/template> \u003C!-- app.vue -->\n\u003Ctemplate>\n \u003CNuxtLayout>\n \u003CNuxtPage />\n \u003C/NuxtLayout>\n\u003C/template> \u003C!-- pages/about.vue -->\n\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n layout: 'custom'\n})\n\u003C/script> html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .sbEgt, html code.shiki .sbEgt{--shiki-default:#999999;--shiki-default-font-style:italic;--shiki-dark:#666666;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .sHLBJ, html code.shiki .sHLBJ{--shiki-default:#998418;--shiki-dark:#B8A965}",{"id":107,"title":106,"titles":412,"content":413,"level":204},[],"Nuxt は様々なレンダリングモードをサポートしています。\n具体的には、ユニバーサルレンダリング、クライアントサイドレンダリング、ハイブリッドレンダリングがあります。",{"id":415,"title":106,"titles":416,"content":417,"level":204},"/ja/concepts/rendering-modes#レンダリングモード",[],"Nuxt は様々なレンダリングモードをサポートしています。\n具体的には、ユニバーサルレンダリング、クライアントサイドレンダリング、ハイブリッドレンダリングがあります。 デフォルトでは、ユニバーサルレンダリングが選択されています。\nまた、これらは nuxt.config で簡単に切り替えることができます。",{"id":419,"title":420,"titles":421,"content":422,"level":214},"/ja/concepts/rendering-modes#ユニバーサルレンダリング","ユニバーサルレンダリング",[106],"ユニバーサルレンダリングでは、サーバーが完全にレンダリングされた HTML を返します。\nこれにより、ユーザーはアプリケーションのコンテンツをすぐに取得することができます。 また、ブラウザ上で HTML を読み込んだ後、インタラクションを必要とする動的な UI を構築するためにブラウザ上で JavaScript コードを読み込みます。\nこのことを「ハイドレーション」と呼びます。 この例では app.vue で count と言うステートをコンソールに出力しています。\nこのページにアクセスした際にターミナル(サーバー)とブラウザのコンソールの両方でこの出力を確認することができます。 これはつまり、Nuxt が HTML をサーバー上で生成するために Vue.js のコードをサーバー上で実行し、その後ブラウザでも同じコードが実行されていることを意味します。\nこれゆえに「ユニバーサルレンダリング」と呼ばれています。 ユニバーサルレンダリングの利点と欠点は主に以下の通りです。",{"id":424,"title":425,"titles":426,"content":427,"level":302},"/ja/concepts/rendering-modes#利点","利点",[106,420],"パフォーマンス\nサーバーサイドで HTML を生成し、それをブラウザが読み込むため、ブラウザ上で JavaScript によってコンテンツを生成するよりも高速です。検索エンジン最適化 (SEO)\nWeb クローラはページのコンテンツを直接インデックスできるため、SEO に有利です。",{"id":429,"title":430,"titles":431,"content":432,"level":302},"/ja/concepts/rendering-modes#欠点","欠点",[106,420],"開発の制約\nサーバーサイドとクライアントサイドでシームレスに動作するコードを書くためにいくつかの制約があります。コスト\nサーバーを必要とするため、サーバーの稼働コストがかかります。 より詳細な説明については 公式ドキュメント を参照してください。",{"id":434,"title":435,"titles":436,"content":437,"level":214},"/ja/concepts/rendering-modes#クライアントサイドレンダリング","クライアントサイドレンダリング",[106],"nuxt.config で ssr: false を設定することで、クライアントサイドレンダリングを有効にすることができます。 // nuxt.config.ts\nexport default defineNuxtConfig({\n ssr: false\n}) クライアントサイドレンダリングでは、アプリケーションをブラウザ上でレンダリングします。\nブラウザが現在のインターフェイスを作成するための命令を含むすべての JavaScript コードをダウンロードして解析した後、HTML 要素を生成します。 クライアントサイドレンダリングの利点と欠点は主に以下の通りです。",{"id":439,"title":425,"titles":440,"content":441,"level":302},"/ja/concepts/rendering-modes#利点-1",[106,435],"開発スピード\nサーバーサイドとのシームレスな動作を要求しないため、ブラウザで動作することだけを考慮して開発することができます。安価\nサーバーを必要としないため、インフラストラクチャのコストがかかりません。オフライン\nコードはすべてブラウザで実行されるため、インターネットが利用できない状態でもうまく動作し続けることができます。",{"id":443,"title":430,"titles":444,"content":445,"level":302},"/ja/concepts/rendering-modes#欠点-1",[106,435],"パフォーマンス\nユーザーはブラウザが JavaScript ファイルをダウンロード、解析、実行するのを待たなければなりません。それらに時間がかかり、ユーザーの体験に影響を与える可能性があります。検索エンジン最適化 (SEO)\nクライアントサイドレンダリングで配信されたコンテンツのインデックス化と更新には時間がかかるため、サーバーレンダリングの HTML ドキュメントと比べ SEO に不利です。",{"id":447,"title":448,"titles":449,"content":450,"level":214},"/ja/concepts/rendering-modes#ハイブリッドレンダリング","ハイブリッドレンダリング",[106],"Nuxt ではルートのルールを設定することにより、ルートごとに異なるキャッシュ ルールとレンダリングモードを選択することができます。 設定は nuxt.config の routeRules オプションで行います。 詳細は 公式ドキュメント を参照してください。 export default defineNuxtConfig({\n routeRules: {\n // Homepage pre-rendered at build time\n '/': { prerender: true },\n // Blog posts page generated on demand, revalidates in background, cached on CDN for 1 hour (3600 seconds)\n '/blog': { isr: 3600 },\n // Blog post page generated on demand once until next deployment, cached on CDN\n '/blog/**': { isr: true },\n // Admin dashboard renders only on client-side\n '/admin/**': { ssr: false },\n }\n})",{"id":452,"title":246,"titles":453,"content":454,"level":214},"/ja/concepts/rendering-modes#チャレンジ",[106],"クライアントサイドレンダリングを設定し、Vue.js のコードがブラウザ上のみで実行されることを確認してみましょう。 そのためには: nuxt.config に ssr: false を設定します。app.vue で count と言うステートをコンソールに出力します。ターミナルでコンソールの出力が行われていないことを確認します。ブラウザの開発者ツールを開き、コンソールの出力を確認します。 次に、ハイブリッドレンダリングを設定し、ルートごとに異なるキャッシュ ルールとレンダリングモードを選択することができることを確認してみましょう。 そのためには: app.vue で NuxtPage コンポーネントを使って、ページをレンダリングします。/pages/index.vue と /pages/foo.vue で、script setup 内コンソールの出力を行います。(内容は任意のもので構いません)nuxt.config に routeRules を設定し、/ と /foo に対して異なるキャッシュ ルールとレンダリングモードを設定します。\n今回は、/foo に ssr: false を設定してみましょう。/ にアクセスし、コンソールの出力がサーバーとクライアントの両方で行われていることを確認します。/foo にアクセスし、コンソールの出力がブラウザのみで行われていることを確認します。 レンダリングモードの動作の確認は、実際には console の出力場所だけではなく、開発者ツールのネットワークタブなどを使って、\nリクエストの挙動を確認することも重要です。ユニバーサルレンダリングの場合はサーバーが HTML を生成するため、ネットワークタブを確認してみるとコンテンツ内容 (主に \u003Cdiv id=\"__nuxt\"> 内)が完全なものになっているのに対し、クライアントサイドレンダリングの場合は、HTML が空の状態で JavaScript によってコンテンツが生成されていることが確認できます。\n(ダウンロードした JavaScript も別のリクエストとして観測することができます) html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sHLBJ, html code.shiki .sHLBJ{--shiki-default:#998418;--shiki-dark:#B8A965}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}",{"id":114,"title":113,"titles":456,"content":457,"level":204},[],"Vue.js での状態管理 (State Management) とは、アプリケーションでリアクティブな状態 (ステート) を管理することを言います。Vue.js 公式ドキュメント 状態管理",{"id":459,"title":113,"titles":460,"content":461,"level":204},"/ja/concepts/state-manegement#状態管理",[],"Vue.js での状態管理 (State Management) とは、アプリケーションでリアクティブな状態 (ステート) を管理することを言います。Vue.js 公式ドキュメント 状態管理 複数のコンポーネント間で状態を共有する際、Vue.js ではリアクティビティー API を用いたシンプルな状態管理を行うことができます。Vue.js 公式ドキュメント リアクティビティー API によるシンプルな状態の管理 しかし、SSR の考慮 にも書かれているように、Nuxt で SSR を行っている場合には いくつかの問題 が起こる可能性があります。 Vue.js の公式ドキュメントでは Pinia と言う状態管理ライブラリを使う方法が紹介されていますが、Nuxt が提供する useState() コンポーザブルもその解決策の 1 つです。\n(もちろん、Nuxt で Pinia を使用する ことも可能です)",{"id":463,"title":464,"titles":465,"content":466,"level":214},"/ja/concepts/state-manegement#usestate","useState()",[113],"useState() コンポーザブル は SSR フレンドリーな状態管理と、コンポーネント間で状態を共有するためのシンプルな方法を提供します。useState() は共有状態を定義するための SSR フレンドリーな ref() です。\n前述の通り、Vue.js のリアクティビティー API (e.g. ref()) を用いてコンポーネントを跨いだ状態管理を SSR で行う場合、いくつかの問題が発生する可能性があります。\nそのため、Nuxt では \u003Cscript setup> や setup() 関数の 外 では、ref() での状態を定義せず、代わりに useState() を使用することがベストプラクティスとされています。 このプレイグラウンドの例では、\"count\" をキーに CounterA コンポーネントと CounterB コンポーネントで状態を共有しています。CounterA によってレンダリングされたボタンをクリックしたときに、CounterB のステートも更新されることに注目してみてください。 より詳細な説明は、Nuxt 公式ドキュメント 状態管理 を参照してください。 useState() のデータは JSON にシリアライズされるため、クラス、関数、シンボルなど、シリアライズできないものを含まないことに注意が必要です。",{"id":121,"title":120,"titles":468,"content":469,"level":204},[],"実用的なアプリケーションを作る上で、データフェッチは欠かせない機能です。\nデータフェッチとは API やデータベースからデータを取得してくることを指します。",{"id":471,"title":120,"titles":472,"content":473,"level":204},"/ja/concepts/data-fetching#データフェッチ",[],"実用的なアプリケーションを作る上で、データフェッチは欠かせない機能です。\nデータフェッチとは API やデータベースからデータを取得してくることを指します。 Nuxt では、このデータフェッチを便利に扱うために useFetch、 useAsyncData、$fetch といった関数を提供しています。 一言で言えば、 useFetch は、コンポーネントのセットアップ関数でデータのフェッチを処理する最も簡単な方法です。$fetch は、ユーザーのインタラクションに基づいてネットワークリクエストを行うのに最適です。useAsyncData は、$fetch と組み合わせることで、よりきめ細かい制御を提供します。 https://nuxt.com/docs/getting-started/data-fetching 中でも、useFetch は最も簡単な方法で、実際には useAsyncData と $fetch の便利なラッパーです。 使い方は以下の通りで、 \u003Cscript setup lang=\"ts\">\nconst { data, pending, error, refresh, clear } = await useFetch('/api/modules')\n\u003C/script> 具体的には以下のような機能があります。 サーバーとクライアントの両方で動作する\nuseFetch はサーバーとクライアントの両方で動作することができるので、ユニバーサルレンダリング時でも簡単にデータフェッチを行うことができます。データキャッシュ\nサーバー上で API が呼ばれた時、そのデータをクライアントに転送することで、クライアント側で再度データフェッチが行われることを防ぎます。リクエスト URL とレスポンスの型付け\nserver ディレクトリに API を実装することで、リクエスト URL とレスポンスの型付けが自動的に行われます。 より詳細な使い方については、公式ドキュメント を参照してください。 また、より細かい制御を行いたい場合は useAsyncData や $fetch を利用することで、より高度なデータフェッチを行うことができます。 https://nuxt.com/docs/api/composables/use-async-data https://nuxt.com/docs/api/utils/dollarfetch",{"id":475,"title":246,"titles":476,"content":477,"level":214},"/ja/concepts/data-fetching#チャレンジ",[120],"API の動作を確かめてみる\nserver/api/todos/index.ts に 4 つめの Todo を追加した後、リフレッシュボタンを押してデータが更新されることを確認してみましょう。型付の自動化を確認してみる\nserver/api/todos/index.ts の Todo に completed プロパティを追加し、useFetch の型が更新されていることを確認してみましょう。 html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":133,"title":132,"titles":479,"content":480,"level":204},[],"このチュートリアルでは、簡易的な「TODOリスト」アプリケーションを作りながら、Vue.js の基礎を体系的に学びます。",{"id":482,"title":132,"titles":483,"content":484,"level":204},"/ja/workspace/todo-list#todoリスト",[],"このチュートリアルでは、簡易的な「TODOリスト」アプリケーションを作りながら、Vue.js の基礎を体系的に学びます。 ブラウザだけで Nuxt と Vue を使った開発を体験できるため、環境構築に時間をかけずに、すぐに学習を始められます。",{"id":486,"title":487,"titles":488,"content":489,"level":214},"/ja/workspace/todo-list#学習の流れ","学習の流れ",[132],"本チュートリアルは、アプリを完成させるステップごとに章が分かれています。\n各章では、Vue.js の主要な機能や概念をひとつずつ学びながら進めます。 項目学べること1. リアクティビティ (1)ref による状態管理と、データ変更時にビューが自動更新される仕組み2. リストレンダリングv-for を使った配列やリストの繰り返し表示3. 条件付きレンダリングv-if / v-else による表示・非表示の切り替え4. コンポーネント化コンポーネント分割5. コンポーネント間でのデータ受け渡しprops・emit・v-on をコンポーネント間でのデータ受け渡し6. 双方向データバインディングv-model と defineModel によるフォーム入力とのデータ連動7. リアクティビティ (2)computed によるリアクティブな算出プロパティの利用8. 独自コンポーネントでの v-model独自コンポーネントで双方向バインディングを実装する方法9. スロットコンテンツ・スロットアウトレットコンポーネントにスロット機能をつける",{"id":491,"title":492,"titles":493,"content":494,"level":214},"/ja/workspace/todo-list#使用する技術","使用する技術",[132],"このチュートリアルでは、以下の技術・ツールを使用します。 TypeScriptJavaScript のスーパーセットで、静的型付けを特徴とする言語です。Web開発におけるデファクトスタンダードのため、本チュートリアルでも採用しています。Vue.js本チュートリアルは、バージョン 3.5.18 で動作確認しています。Vue コンポーネントは、Composition API (\u003Cscript setup>) スタイルを採用しています。Nuxt本チュートリアルは Nuxt 上で構築されていますが、Nuxt 特有の機能は使用しません。",{"id":139,"title":29,"titles":496,"content":497,"level":204},[],"Vue はデータの変更を監視して、変更された時に更新を自動的にトリガーする 優れたリアクティビティシステム を提供していて、常に最新のデータを UI に反映させることができます。Vue のリアクティビティは、ref、computed、watch があります。\nここでは、refについて学習します。",{"id":499,"title":29,"titles":500,"content":497,"level":204},"/ja/workspace/todo-list/reactivity-1#リアクティビティー-パート1",[],{"id":502,"title":503,"titles":504,"content":505,"level":214},"/ja/workspace/todo-list/reactivity-1#ref-の基本","ref の基本",[29],"ref() は単一の値を保持するためのコンテナを作成し、値が変更された時に自動的に追跡できるようにします。値は .value を通してアクセスすることができます。 \u003Cscript setup>\nimport { ref } from 'vue'\n\nconst userName = ref('')\n\nfunction setNameUser1() {\n userName.value = 'ユーザー1'\n}\n\nfunction setNameUser2() {\n userName.value = 'ユーザー2'\n}\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cdiv>\n \u003Cp>ユーザー名: {{ userName }}\u003C/p>\n\n \u003Cbutton @click=\"setNameUser1\">\n ユーザー1をセット\n \u003C/button>\n \u003Cbutton @click=\"setNameUser2\">\n ユーザー2をセット\n \u003C/button>\n \u003C/div>\n\u003C/template>",{"id":507,"title":508,"titles":509,"content":510,"level":214},"/ja/workspace/todo-list/reactivity-1#現在の実装の課題","現在の実装の課題",[29],"プレイグラウンドでlet userNameを用意し、「ユーザー名をセット」ボタンを押したらuserNameに任意のユーザー名が入るようにしています。 \u003Cscript setup lang='ts'>\nlet userName = 'No Name'\n\nfunction setUserName() {\n userName = 'Vue Fes Japan'\n}\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cdiv class=\"header-right\">\n \u003Cbutton @click=\"setUserName\">\n ユーザー名をセット\n \u003C/button>\n 👤\n \u003Cspan>{{ userName }}\u003C/span>\n \u003C/div>\n\u003C/template> この方法では: userNameがリアクティブではないため、値の変化をVueのリアクティブシステムが検知できない。userName = 'Vue Fes Japan'のように値を変えても、画面への自動更新が起きない。",{"id":512,"title":246,"titles":513,"content":514,"level":214},"/ja/workspace/todo-list/reactivity-1#チャレンジ",[29],"userNameをrefを使ってリアクティブな値にし、値の変化を検知できるようにしてください。 let userNameを ref()を使ってリアクティブな値にしてくださいuserName = \"Vue Fes Japan\"を.value を通してアクセスするよう変更してください \u003Ctemplate>内では自動的に.valueがアンラップされるため、{{ userName }}と書くだけでOKです。\nまた、refはリアクティブなコンテナなので、constで宣言しても問題ありません。",{"id":516,"title":517,"titles":518,"content":519,"level":214},"/ja/workspace/todo-list/reactivity-1#実装後の効果","実装後の効果",[29],"ref を使用すると: データの変更をVueが自動で検知し、UIがリアルタイムに更新されるようになった変更を反映するための手動操作が不要になり、開発効率が向上した もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。 ref を使い、データの変更が自動的に画面に反映されるようになりました! html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":146,"title":145,"titles":521,"content":522,"level":204},[],"Vueでは v-for ディレクティブを使って配列に基づいて項目のリストをレンダリングできます。これは ToDoリストのようなデータの一覧表示に非常に便利です。",{"id":524,"title":145,"titles":525,"content":522,"level":204},"/ja/workspace/todo-list/list-rendering#リストレンダリング",[],{"id":527,"title":528,"titles":529,"content":530,"level":214},"/ja/workspace/todo-list/list-rendering#v-for-の基本","v-for の基本",[145],"v-for ディレクティブでは、item in items という形式の特別な構文が必要になります。ここで、items は元のデータの配列を指し、item は反復処理の対象となっている配列要素のエイリアスを指します: \u003Cli v-for=\"item in items\" :key=\"item.id\">\n {{ item.message }}\n\u003C/li> 重要なポイント: :key 属性を指定することで、Vue に各ノードを一意に追跡するためのヒントを与えますitem in items の形式で、配列の各要素を順番に処理します",{"id":532,"title":508,"titles":533,"content":534,"level":214},"/ja/workspace/todo-list/list-rendering#現在の実装の課題",[145],"プレイグラウンドで ToDo データをテーブルで表示していますが、配列の1件目のデータ todos[0] のみを直接参照して表示しています: \u003Ctr>\n \u003Ctd>{{ todos[0].done }}\u003C/td>\n \u003Ctd>{{ todos[0].title }}\u003C/td>\n \u003Ctd>{{ todos[0].note }}\u003C/td>\n \u003Ctd>{{ todos[0].dueDate }}\u003C/td>\n\u003C/tr> この方法では: 1件目のデータしか表示されないデータが増えても自動で表示されない",{"id":536,"title":246,"titles":537,"content":538,"level":214},"/ja/workspace/todo-list/list-rendering#チャレンジ",[145],"現在の固定インデックス [0] による参照を v-for を使った動的なレンダリングに変更してください: \u003Ctbody> 内の固定の \u003Ctr> 要素を v-for を使って書き換えますv-for=\"todo in todos\" を使って全ての ToDoアイテムを動的にレンダリングします各項目に一意の :key=\"todo.id\" 属性を指定します key 属性は、Vue に各ノードを一意に追跡するためのヒントを与え、既存の要素を再利用して並べ替えを適用できるようにする際に必要となります。",{"id":540,"title":517,"titles":541,"content":542,"level":214},"/ja/workspace/todo-list/list-rendering#実装後の効果",[145],"v-for を実装すると: 配列内の全てのToDoアイテムが表示されるデータが追加・削除されても自動で表示が更新されるより保守性の高いコードになる もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。 v-for を使うことで動的なデータ表示がとても簡単になりましたね! html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":153,"title":152,"titles":544,"content":545,"level":204},[],"Vueでは v-if ディレクティブを使って、ブロックを条件に応じてレンダリングできます。ブロックは、ディレクティブの式が真を返す場合のみレンダリングされます。",{"id":547,"title":152,"titles":548,"content":545,"level":204},"/ja/workspace/todo-list/conditional#条件付きレンダリング",[],{"id":550,"title":551,"titles":552,"content":553,"level":214},"/ja/workspace/todo-list/conditional#v-if-の基本","v-if の基本",[152],"v-if ディレクティブは、指定した式が真の場合のみ要素をレンダリングします。v-else は v-if に対する \"else block\" を示すために使用できます: \u003Cbutton v-if=\"todo.done\" type=\"button\">\n ✅\n\u003C/button>\n\n\u003Cbutton v-else type=\"button\">\n ⬜\n\u003C/button> 重要なポイント: v-else 要素は、v-if 要素の直後になければなりません条件の式が真の場合のみ、該当する要素がレンダリングされます",{"id":555,"title":508,"titles":556,"content":557,"level":214},"/ja/workspace/todo-list/conditional#現在の実装の課題",[152],"プレイグラウンドでは、ToDoリストの「完了」欄にボタンとアイコン(例として絵文字を使います)を表示する部分で、まだ条件分岐ができていません: \u003Ctd class=\"text-center\">\n \u003C!-- ここに v-if/v-else でアイコンを切り替える処理を追加 -->\n {{ todo.done }}\n\u003C/td> 現在は固定のテキストが表示されているだけで、ToDoの完了状態に応じたアイコンの切り替えができていません。",{"id":559,"title":246,"titles":560,"content":561,"level":214},"/ja/workspace/todo-list/conditional#チャレンジ",[152],"\u003Ctd class=\"text-center\"> 内に v-if と v-else を使って、完了状態(todo.done)に応じて異なるボタンとアイコンを表示してください: todo.done が true の場合:アイコン✅を含むボタンを表示todo.done が false の場合:アイコン⬜を含むボタンを表示各ボタンには適切な alt 属性を設定してアクセシビリティを向上させます 参考実装: \u003Cbutton type=\"button\" class=\"button-icon\">\n ✅\n\u003C/button>\n\n\u003Cbutton type=\"button\" class=\"button-icon\">\n ⬜\n\u003C/button> v-else 要素は、v-if 要素の直後になければなりません。それ以外の場合は認識されません。",{"id":563,"title":517,"titles":564,"content":565,"level":214},"/ja/workspace/todo-list/conditional#実装後の効果",[152],"v-if と v-else を実装すると: 各ToDoの完了状態に応じてアイコンが切り替わります条件付きレンダリングの基本概念が理解できますより動的で視覚的に分かりやすいUIになります もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。 v-if と v-else を使うことで、状態に応じた柔軟なUIが実現できます! html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":160,"title":159,"titles":567,"content":278,"level":204},[],{"id":569,"title":159,"titles":570,"content":278,"level":204},"/ja/workspace/todo-list/componentization-1#コンポーネント化-パート1",[],{"id":572,"title":284,"titles":573,"content":574,"level":214},"/ja/workspace/todo-list/componentization-1#基本的な-sfc-の構造",[159],"SFC は基本的に \u003Cscript setup>, \u003Ctemplate>, \u003Cstyle> の 3 つのセクションで構成されます。 \u003Cscript setup>: コンポーネントのロジック部分を定義します。\u003Cscript setup> を使用することで、Composition API を簡潔に書くことができます。\u003Ctemplate>: コンポーネントのビュー部分を定義します。\u003Cstyle scoped>: コンポーネント固有のスタイルを定義します。scoped 属性を追加することで、このコンポーネントのスタイルが他のコンポーネントに影響を与えないようにします。",{"id":576,"title":289,"titles":577,"content":291,"level":214},"/ja/workspace/todo-list/componentization-1#コンポーネントの再利用",[159],{"id":579,"title":508,"titles":580,"content":581,"level":214},"/ja/workspace/todo-list/componentization-1#現在の実装の課題",[159],"現在、プレイグラウンドのapp.vueに全てのコードがまとまっています。\nこのままでも問題ありませんが、コンポーネント化することでコードの役割分担が明確になり、保守性や再利用性がに向上します。",{"id":583,"title":584,"titles":585,"content":586,"level":214},"/ja/workspace/todo-list/componentization-1#チャレンジ1","チャレンジ1",[159],"app.vueにある\u003Ctable>\u003C/table>をcomponents/TodoList.vueに切り出してみましょう app.vueの\u003Ctable>\u003C/table>をTodoList.vueの\u003Ctemplate>内に移動しましょうapp.vueの/* --- table start --- */から/* --- table last --- */までをTodoList.vueの\u003Cstyle scoped>内に移動しましょうapp.vueでimport構文を使ってcomponents/TodoList.vueを読み込み、\u003Ctemplate>内で\u003CTodoList />として使用しましょう。 もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。 app.vueがすっきりとしました。 ただこのままでは、TodoList.vue側でapp.vueに定義された値にアクセスすることができません。\n次の章で、コンポーネント間のデータの受け渡し方を実装します。 html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":167,"title":166,"titles":588,"content":344,"level":204},[],{"id":590,"title":166,"titles":591,"content":344,"level":204},"/ja/workspace/todo-list/componentization-2#コンポーネント化-パート2",[],{"id":593,"title":294,"titles":594,"content":595,"level":214},"/ja/workspace/todo-list/componentization-2#コンポーネント間のデータの受け渡し",[166],"Vue コンポーネント間でデータを受け渡しする基本的な方法として、props と emit を使用します。",{"id":597,"title":299,"titles":598,"content":301,"level":302},"/ja/workspace/todo-list/componentization-2#props",[166,294],{"id":600,"title":305,"titles":601,"content":602,"level":302},"/ja/workspace/todo-list/componentization-2#emit",[166,294],"子コンポーネントから親コンポーネントにイベントを発火するための方法です。 まずは子コンポーネント側で defineEmits マクロを使用し、発火したいイベントを定義します。\nemit 関数を用いて、イベントを発火することができます。 \u003C!-- Child.vue -->\n\u003Cscript setup lang=\"ts\">\nconst emit = defineEmits\u003C{ sendMessage: [] }>()\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cbutton type=\"button\" @click=\"emit('sendMessage')\">\n Click me\n \u003C/button>\n\u003C/template> 発火されたイベントは親コンポーネント側で v-on ディレクティブを使用して受け取ることができます。 \u003C!-- Parent.vue -->\n\u003Cscript setup lang=\"ts\">\nfunction handleSendMessage() {\n console.log('Message sent!')\n}\n\u003C/script>\n\n\u003Ctemplate>\n \u003CChild @send-message=\"handleSendMessage\" />\n\u003C/template> 以下のように、イベント発火時に子コンポーネントからデータを受け渡すこともできます。 \u003C!-- Child.vue -->\n\u003Cscript setup lang=\"ts\">\nconst emit = defineEmits\u003C{ sendMessage: [string] }>()\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cbutton type=\"button\" @click=\"emit('sendMessage', 'Hello, Vue!')\">\n Click me\n \u003C/button>\n\u003C/template> \u003C!-- Parent.vue -->\n\u003Cscript setup lang=\"ts\">\nfunction handleSendMessage(message: string) {\n console.log(message) // Hello, Vue!\n}\n\u003C/script>\n\n\u003Ctemplate>\n \u003CChild @send-message=\"handleSendMessage\" />\n\u003C/template> それぞれの詳しい API ドキュメントから確認することができます。",{"id":604,"title":508,"titles":605,"content":606,"level":214},"/ja/workspace/todo-list/componentization-2#現在の実装の課題",[166],"app.vueに定義されたtodosにTodoList.vueからアクセスすることができていません。\nprops と emit を使用して、コンポーネント間でデータをやり取りできるようにしましょう。",{"id":608,"title":609,"titles":610,"content":611,"level":214},"/ja/workspace/todo-list/componentization-2#チャレンジ2","チャレンジ2",[166],"props と emit を使用して親子間でデータの受け渡しをできるようにしましょう: TodoList.vueでdefinePropsを使用して親からtodosを受け取れるようにしましょうapp.vueに\u003CTodoList />を配置して、todosを渡してみましょう。TodoList.vueでdefineEmitsを使用してアイコンのクリックイベントを親に伝えられるようにしましょうapp.vueでTodoList.vueから受け取ったイベントを利用してupdateDoneを実行しましょう 参考実装: \u003Cscript setup lang=\"ts\">\n/**\n * Props\n */\ndefineProps\u003C{\n todos: Todo[]\n}>()\n\n/*\n * Emit\n */\nconst emit = defineEmits\u003C{\n updateDone: [number, boolean]\n}>()\n\u003C/script>",{"id":613,"title":517,"titles":614,"content":615,"level":214},"/ja/workspace/todo-list/componentization-2#実装後の効果",[166],"コンポーネント化すると: 関心ごと(表示とロジック)が分割され、コードの見通しが良くなる複数のコンポーネントで同じUIパーツを再利用できるようになる親子間のデータ受け渡し(props・emit)を通じて状態管理が整理され、チーム開発や拡張がしやすくなる もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。 コンポーネント化され、メンテナンスしやすいスッキリした構造になりました! html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}",{"id":174,"title":173,"titles":617,"content":618,"level":204},[],"Vueでは v-model ディレクティブを使うことで、フォーム要素やカスタムコンポーネントと「双方向データバインディング」を簡潔に実現できます。ユーザーの入力とVueのデータが常に同期され、フォーム制御が直感的になります。",{"id":620,"title":173,"titles":621,"content":618,"level":204},"/ja/workspace/todo-list/v-model#双方向データバインディング",[],{"id":623,"title":624,"titles":625,"content":626,"level":214},"/ja/workspace/todo-list/v-model#v-model-の基本","v-model の基本",[173],"v-model ディレクティブは、Vueインスタンスのデータとフォーム要素の値を自動的に同期します。テキスト欄・チェックボックス・ラジオボタン・セレクトボックスはもちろん、独自コンポーネントにも対応しています。 \u003Cinput v-model=\"text\" type=\"text\" />\n\n\u003Cp>\n{{ text }}\n\u003C/p> この例では、入力欄(input)に文字を入力すると text データが即時に変更され、下の \u003Cp> に反映されます。text の値をJavaScriptから変更すると、input欄の表示も自動的に変わります。",{"id":628,"title":629,"titles":630,"content":631,"level":214},"/ja/workspace/todo-list/v-model#仕組み","仕組み",[173],"v-model は実際には「バインド」と「イベント監視」を合わせた“糖衣構文”です: \u003Cinput v-model=\"message\" />\n\n\u003C!-- と同じ -->\n\u003Cinput :value=\"message\" @input=\"message = $event.target.value\" /> フォームタイプごとにバインドされる属性やイベントが異なります: テキスト・テキストエリア:value 属性と input イベントチェックボックス・ラジオ:checked 属性と change イベントセレクトボックス:value 属性と change イベント たとえばテキスト入力フォームにはこう使います: \u003Cinput v-model=\"newTodoText\" type=\"text\" placeholder=\"新しいタスクを入力\" />\n\n\u003Cbutton @click=\"addTodo\">\n追加\n\u003C/button> v-model=\"newTodoText\" で、入力内容が常に newTodoText 変数に格納されます。",{"id":633,"title":634,"titles":635,"content":636,"level":214},"/ja/workspace/todo-list/v-model#コンポーネントとの連携","コンポーネントとの連携",[173],"独自コンポーネントでも v-model は使えます。Vue 3.4以降なら defineModel() マクロにより、親子間の値・イベント同期が一層シンプルに書けます。(こちらは、コンポーネント化 パート2でで学習します。) \u003C!-- 親コンポーネント -->\n\u003CMyInput v-model=\"searchTerm\" />\n\n\u003C!-- MyInput.vue(子コンポーネント) -->\n\u003Cscript setup>\nconst modelValue = defineModel()\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cinput v-model=\"modelValue\">\n\u003C/template>",{"id":638,"title":639,"titles":640,"content":641,"level":214},"/ja/workspace/todo-list/v-model#注意点とコツ","注意点とコツ",[173],"v-modelとHTMLの value を併用するとvalueは無効になりますv-model とイベントハンドラ(例:@change)を併用すると動作に注意が必要データの加工やカスタム検証が必要な場合は、「v-bind」と「v-on」で個別制御が便利",{"id":643,"title":246,"titles":644,"content":645,"level":214},"/ja/workspace/todo-list/v-model#チャレンジ",[173],"今のプレイグラウンドでは、「未完了のみ」チェックボックスを実装しようとしています。 \u003Cinput :value=\"showUnDoneOnly\" type=\"checkbox\" />\n未完了のみ表示 ただし、この状態ではチェックボックスの「チェックが入っているかどうか(checked)」は VueのデータshowUnDoneOnlyと連動しません。\n以下手順に沿って、まずは バインディングで値が同期される仕組みを体験し、次に v-model で双方向に値が同期される使い方を学びましょう。 \u003Cinput>の@changeイベントを使い、ユーザーがチェックボックスをクリックしたタイミングでshowUnDoneOnlyの値を切り替えましょう。これにより、showUnDoneOnlyが操作に連動して変更されるようになります。 \u003Cinput\n :value=\"showUnDoneOnly\"\n type=\"checkbox\"\n @change=\"showUnDoneOnly = !showUnDoneOnly\"\n/> 次に、:valueをv-modelに切り替えて、@changeイベントでの値の変更は削除しましょう。そうすることで、Vueの双方向バインディングによって値が自動的に同期されるようになります。",{"id":647,"title":517,"titles":648,"content":649,"level":214},"/ja/workspace/todo-list/v-model#実装後の効果",[173],"入力欄やチェックボックスとデータがリアルタイムに連動フォームの状態管理がシンプルかつ直感的にバリデーションやカスタムイベントも組み合わせて応用可能 v-modelを使って直感的にフォーム機能を使えるようになりました! html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}",{"id":180,"title":36,"titles":651,"content":652,"level":204},[],"リアクティビティー パート1で、データの変更を監視して、変更された時に更新を自動的にトリガーする 優れたリアクティビティシステム のrefについて学習しました。\nここでは、computedについて学習しましょう。",{"id":654,"title":36,"titles":655,"content":652,"level":204},"/ja/workspace/todo-list/reactivity-2#リアクティビティー-パート2",[],{"id":657,"title":658,"titles":659,"content":660,"level":214},"/ja/workspace/todo-list/reactivity-2#computed-の基本","computed の基本",[36],"Vue には computed プロパティ という仕組みがあり、\n既存のデータをもとに新しい値を“自動で計算”し、かつキャッシュしてくれるため、パフォーマンス良く使えます。\ncomputed は「依存している値」が変化した時だけ再計算されます。 \u003Cscript setup>\nimport { computed, ref } from 'vue'\n\nconst score = ref(65)\nconst grade = computed(() => score.value >= 80 ? 'A' : 'B')\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cp>成績:{{ grade }}\u003C/p>\n\u003C/template> score が変わると自動で grade が自動で再計算されます。\n逆に score が変わらない限り、何度使っても前回の計算結果を使います。 値はrefと同じく .value を通してアクセスすることができます。\nただし\u003Ctemplate>内では自動的に.valueがアンラップされるため、{{ grade }}と書くだけでOKです。",{"id":662,"title":629,"titles":663,"content":664,"level":214},"/ja/workspace/todo-list/reactivity-2#仕組み",[36],"computed の本質は getter 関数 です。\n依存するリアクティブデータを監視し、変更があったときだけ処理を実行します。\n以下のような流れです: 初回アクセス時に計算依存している値が変わるまで結果をキャッシュ依存値が変わると再計算 これは、**通常の関数呼び出し(methods)**とは大きく異なります。methodsは呼ばれるたびに処理されますが、computedはキャッシュされます。",{"id":666,"title":667,"titles":668,"content":669,"level":214},"/ja/workspace/todo-list/reactivity-2#よくある使い方","よくある使い方",[36],"フォーマット変換(例:日付や通貨の整形)条件によるラベル表示(合格/不合格 など)配列やリストのフィルタリング・ソート数値や文字列の結合・集計",{"id":671,"title":672,"titles":673,"content":674,"level":214},"/ja/workspace/todo-list/reactivity-2#双方向writable-computed","双方向(writable computed)",[36],"computed は getter だけでなく setter も定義できます。\nこれにより、算出値を書き換えたときに元のデータに反映できます。 \u003Cscript setup>\nimport { computed, ref } from 'vue'\n\nconst count = ref(2)\nconst doubled = computed({\n get: () => count.value * 2,\n set: (val) => {\n count.value = val / 2\n }\n})\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cinput v-model=\"doubled\" type=\"number\">\n \u003Cp>count: {{ count }}\u003C/p>\n\u003C/template> この場合、doubled を変更すると自動的に count も変わります。",{"id":676,"title":246,"titles":677,"content":678,"level":214},"/ja/workspace/todo-list/reactivity-2#チャレンジ",[36],"前回の学習では、「未完了のみ」チェックボックスを実装し、showUnDoneOnly と値が同期されるようにしました。\n今回は、チェックが入っているときは done が true の Todo のみ、\nチェックが外れているときは すべての Todo を表示するよう、filteredTodos を実装してみましょう。 \u003Cscript setup>の中で、新しく filteredTodos という算出プロパティ(computed)を定義しましょう。この中で、showUnDoneOnly の値によって表示するTodoリストを切り替えるロジックを書きます。showUnDoneOnlyがtrueの場合は「done: true のTodoのみ」を返し、showUnDoneOnlyがfalseの場合は「すべてのTodo」を返すようにコードを組みましょう。今までは :todos=\"todos\" で直接全てのリストを渡していましたが、\n実装した filteredTodos を TodoList コンポーネントに渡してみましょう。",{"id":680,"title":681,"titles":682,"content":683,"level":302},"/ja/workspace/todo-list/reactivity-2#実装例","実装例",[36,246],"\u003Cscript setup>\nimport { computed, ref } from 'vue'\n\nconst filteredTodos = computed(() => {\n // チェックが外れている場合は全て表示\n if (!showUnDoneOnly.value) {\n return todos.value\n }\n\n // チェックが入っている場合は「未完了だけ」\n return todos.value.filter(todo => !todo.done)\n})\n\u003C/script>",{"id":685,"title":517,"titles":686,"content":687,"level":214},"/ja/workspace/todo-list/reactivity-2#実装後の効果",[36],"依存している値が変わった時だけ自動で再計算される計算結果がキャッシュされ、無駄な処理を減らせる加工や条件分岐のロジックをまとめられ、テンプレートがすっきりする もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。 チェックボックスを切り替えた時に、表示されるTodoが正しく絞り込まれるようになりました! html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}",{"id":187,"title":186,"titles":689,"content":690,"level":204},[],"双方向データバインディングで紹介したように、v-modelはフォーム入力欄とVueのデータを自動で同期してくれる仕組みです。\nこのv-modelは、独自のコンポーネントでも同じように使うことができます。",{"id":692,"title":186,"titles":693,"content":694,"level":204},"/ja/workspace/todo-list/componentization-3#独自コンポーネントでの-v-model",[],"双方向データバインディングで紹介したように、v-modelはフォーム入力欄とVueのデータを自動で同期してくれる仕組みです。\nこのv-modelは、独自のコンポーネントでも同じように使うことができます。 \u003C!-- 親コンポーネント -->\n\u003Cscript setup>\nconst count = ref(10)\n\u003C/script>\n\n\u003Ctemplate>\n \u003CClearButton v-model=\"count\" />\n\u003C/template>",{"id":696,"title":697,"titles":698,"content":699,"level":302},"/ja/workspace/todo-list/componentization-3#どうやってつながっているの","どうやってつながっているの?",[186],"v-modelは、親と子の間で「親から子へ値を渡し、子で変更があれば親に返す」というやり取りをできるようにしてくれる仕組みです。\n具体的には次の動きができるようになります。 親から子へ、変更可能な値をpropsで渡します(props名は自動的にmodelValueになります)子で値を変更するために、emit を使って親に通知します(イベント名は自動でupdate:modelValueになります) こうすることで、親と子のデータが常に同期されるようになります。 この仕組みを子コンポーネントで使うときは、propsとemitsを明示的に定義します。 \u003Cscript setup>\nconst props = defineProps(['modelValue']) // 親から値を受け取る\nconst emit = defineEmits(['update:modelValue']) // 変更を親に通知する\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cbutton @click=\"emit('update:modelValue', 0)\">\n カウントリセット\n \u003C/button>\n\u003C/template>",{"id":701,"title":702,"titles":703,"content":704,"level":214},"/ja/workspace/todo-list/componentization-3#vue-34以降の新しい書き方definemodel","Vue 3.4以降の新しい書き方:defineModel()",[186],"上の方法では、親子間で値を自動的に同期できますが、子コンポーネントの記述が少し複雑です。 Vue 3.4以降では、defineModel()マクロを使うことで、子コンポーネント側でmodelValueを変更するだけで済みます(内部的には従来と同じprops/emitが自動的に使われています)\nなお、内部的にはmodelValueという名前が使われていますが、変数名は子側で自由に変更できます。 \u003C!-- 親コンポーネント -->\n\u003Cscript setup>\nconst count = ref(0)\n\u003C/script>\n\n\u003Ctemplate>\n \u003CClearButton v-model=\"count\" />\n\u003C/template> \u003C!-- 子コンポーネント -->\n\u003Cscript setup>\nconst count = defineModel()\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cbutton @click=\"count = 0\">\n カウントリセット\n \u003C/button>\n\u003C/template> ポイント propsやemitを書かなくてもOK親と子の値がリアルタイムでつながる",{"id":706,"title":707,"titles":708,"content":709,"level":214},"/ja/workspace/todo-list/componentization-3#複数の-v-model名前付き-v-model","複数の v-model(名前付き v-model)",[186],"v-modelは、ひとつの値だけでなく、名前を付けて複数の値を同時に親子でやりとりすることもできます。\n複雑なフォームの時にとても便利です。 \u003C!-- 親コンポーネント -->\n\u003Ctemplate>\n \u003CBookEditor\n v-model:title=\"bookTitle\"\n v-model:author=\"bookAuthor\"\n />\n\u003C/template> \u003C!-- 子コンポーネント(difneModel使用) -->\n\u003Cscript setup>\nconst title = defineModel('title')\nconst author = defineModel('author')\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cinput v-model=\"title\" placeholder=\"タイトル\">\n \u003Cinput v-model=\"author\" placeholder=\"著者\">\n\u003C/template> \u003C!-- 子コンポーネント(props, emitをを明示的に定義) -->\n\u003Cscript setup>\nconst props = defineProps([\n 'title',\n 'author'\n])\nconst emit = defineEmits([\n 'update:title',\n 'update:author'\n])\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cinput\n :value=\"props.title\"\n @input=\"emit('update:title', $event.target.value)\"\n >\n \u003Cinput\n :value=\"props.author\"\n @input=\"emit('update:author', $event.target.value)\"\n >\n\u003C/template> ポイント 親は v-model:名前 でいくつでも決められる子は defineModel('名前') で対応できるVue 3.4未満なら、props/emitsを一つずつ定義する必要あり",{"id":711,"title":712,"titles":713,"content":714,"level":214},"/ja/workspace/todo-list/componentization-3#まとめ","まとめ",[186],"v-modelは propsで値を渡し、emitで変更を返す 仕組みをまとめた構文Vue 3.4以降は defineModel()で記述がさらに簡単複数の値や名前付きも、同じ構文でシンプルに扱える従来のフォーム要素と同じ感覚で、独自コンポーネントでも使える",{"id":716,"title":246,"titles":717,"content":718,"level":214},"/ja/workspace/todo-list/componentization-3#チャレンジ",[186],"新規 TODO を追加するモーダルコンポーネント(CreateModal.vue)を作成しましょう。\napp.vue では isCreateModalOpen という状態を用意していて、「新規作成」ボタンを押すとモーダルが開くようになっています。\nここでは、モーダルの「閉じる」ボタンでモーダルを閉じられるようにするのが目標です。 子コンポーネントで defineModel() を使って、modelValue(または任意の名前)を定義するv-if を使って、その値が true のときだけモーダルを表示するようにするモーダルの「閉じる」ボタンをクリックすると、この値が false になるようにする親コンポーネントから v-model で isCreateModalOpen を子に渡して動作を確認する もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。 親・子コンポーネント間で isCreateModalOpen を双方向に同期できるようになりました! html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}",{"id":194,"title":193,"titles":720,"content":721,"level":204},[],"v-slot は、親コンポーネントから子コンポーネントの特定の場所にテンプレートを差し込むための仕組みです。\nコンポーネントを「箱」として使い、その中に好きなHTMLや部品を入れることができます。",{"id":723,"title":193,"titles":724,"content":725,"level":204},"/ja/workspace/todo-list/v-slot#独自コンポーネントでの-v-slot",[],"v-slot は、親コンポーネントから子コンポーネントの特定の場所にテンプレートを差し込むための仕組みです。\nコンポーネントを「箱」として使い、その中に好きなHTMLや部品を入れることができます。 \u003C!-- 子コンポーネント -->\n\u003Ctemplate>\n \u003Cdiv class=\"box\">\n \u003Cslot />\n \u003C/div>\n\u003C/template> \u003C!-- 親コンポーネント -->\n\u003Ctemplate>\n \u003CMyBox>\n \u003Cp>ここが中身になります\u003C/p>\n \u003C/MyBox>\n\u003C/template>",{"id":727,"title":728,"titles":729,"content":730,"level":214},"/ja/workspace/todo-list/v-slot#チャレンジ1","チャレンジ1",[193],"CreateModal.vue に を追加し、\napp.vue 側でそのスロットに表示させたいテキストを入れてみましょう。",{"id":732,"title":733,"titles":734,"content":735,"level":214},"/ja/workspace/todo-list/v-slot#名前付きスロット","名前付きスロット",[193],"スロットに名前を付けると、1つのコンポーネントに複数の差し込み場所を作れます。 \u003C!-- 子コンポーネント -->\n\u003Ctemplate>\n \u003Cheader>\n \u003Cslot name=\"header\" />\n \u003C/header>\n \u003Cmain>\n \u003Cslot />\n \u003C/main>\n \u003Cfooter>\n \u003Cslot name=\"footer\" />\n \u003C/footer>\n\u003C/template> \u003C!-- 親コンポーネント -->\n\u003Ctemplate>\n \u003CMyLayout>\n \u003Ctemplate #header>\n \u003Ch1>タイトル\u003C/h1>\n \u003C/template>\n\n \u003Cp>メインコンテンツ部分\u003C/p>\n\n \u003Ctemplate #footer>\n \u003Csmall>フッターの文章\u003C/small>\n \u003C/template>\n \u003C/MyLayout>\n\u003C/template> ポイント \u003Cslot name=\"xxx\" /> に対して \u003Ctemplate #xxx> で中身を渡す名前なしスロット(デフォルト)と混ぜて使える",{"id":737,"title":738,"titles":739,"content":740,"level":214},"/ja/workspace/todo-list/v-slot#子から親にデータを渡すスコープ付きスロット","子から親にデータを渡す(スコープ付きスロット)",[193],"スロット経由で、子から親にデータを渡すこともできます。 \u003C!-- 子コンポーネント -->\n\u003Cscript setup>\nconst greeting = 'こんにちは!'\n\u003C/script>\n\n\u003Ctemplate>\n \u003Cslot :message=\"greeting\" />\n\u003C/template> \u003C!-- 親コンポーネント -->\n\u003Ctemplate>\n \u003CGreetingBox>\n \u003Ctemplate #default=\"{ message }\">\n \u003Cp>{{ message }}\u003C/p>\n \u003C/template>\n \u003C/GreetingBox>\n\u003C/template> ポイント 子は \u003Cslot :名前=\"値\" /> で変数を渡せる親は \u003Ctemplate #default=\"{ 名前 }\"> で受け取る",{"id":742,"title":712,"titles":743,"content":744,"level":214},"/ja/workspace/todo-list/v-slot#まとめ",[193],"v-slot はコンポーネントの中の特定位置にテンプレートを差し込む機能子から親にデータを渡すときはスコープ付きスロット名前付きスロットを使えば差し込み場所を増やせる",{"id":746,"title":747,"titles":748,"content":749,"level":214},"/ja/workspace/todo-list/v-slot#チャレンジ2","チャレンジ2",[193],"先ほど用意したCreateModal.vueの\u003Cslot>に、新規todo入力フォームを差し込んでみましょう。 app.vue側でCreateModal.vueに表示する新規todo入力フォームを作成する 参考実装: \u003Cform>\n \u003Cdiv>\n \u003Clabel for=\"title\">タイトル\u003C/label>\n \u003Cinput id=\"title\" type=\"text\" required />\n \u003C/div>\n\n \u003Cdiv>\n \u003Clabel for=\"note\">メモ\u003C/label>\n \u003Ctextarea id=\"note\" rows=\"2\" />\n \u003C/div>\n\n \u003Cdiv>\n \u003Clabel for=\"dueDate\">期限\u003C/label>\n \u003Cinput id=\"dueDate\" type=\"date\" />\n \u003C/div>\n\n \u003Cdiv>\n \u003Cbutton type=\"button\">登録\u003C/button>\n \u003C/div>\n\u003C/form>",{"id":751,"title":752,"titles":753,"content":754,"level":214},"/ja/workspace/todo-list/v-slot#チャレンジ3","チャレンジ3",[193],"フォームが表示されたら、いよいよ実際にデータを登録できるようにして、TODOリストを完成させましょう!\nこれまで学んだ知識を活かせば実装できます。ぜひ自分のやり方で挑戦してみてください。 実装のヒント(例) 各フォーム入力欄に対応するリアクティブ変数(ref)を用意し、v-modelでフォームと同期させる「登録」ボタンクリックイベントにhandleSubmitなどの関数を渡しますhandleSubmit内で、以下の情報を持つ新しいTodoオブジェクトを作成し、todos.valueの先頭に追加します。\nフォームから入力された値done: false(新規作成時は未完了)id: Date.now()(一意なID) 参考実装: function handleSubmit() {\n const newTodo: Todo = {\n id: Date.now(),\n done: false,\n title: inputTitile.value,\n note: inputNote.value,\n dueDate: inputDate.value\n }\n\n todos.value = [\n newTodo,\n ...todos.value\n ]\n} もし行き詰まったら、以下のボタンをクリックして解答を見ることができます。 ここまでお疲れ様でした!\n基本的なVue.jsの機能を使って、自分でTODOリストを作成できるようになりましたね。 今回の学習で扱った主な内容は以下の通りです: ref(リアクティブなデータの作り方)v-for(リストをループ表示する方法)v-if(条件による表示・非表示の切り替え)コンポーネントの Single File Component(SFC)と \u003Cscript setup>(Composition API の基本)props と emit(親子コンポーネント間のデータ受け渡しとイベント通知)v-model と defineModel(親子での双方向データバインディング)computed(計算されたリアクティブデータ)v-slot(親から子へのテンプレート差し込み)",{"id":756,"title":757,"titles":758,"content":759,"level":302},"/ja/workspace/todo-list/v-slot#これから知っておくと良い機能","これから知っておくと良い機能",[193,752],"今回の学習には含まれていませんが、Vue.js では他にも便利な機能や大事な考え方がいろいろあります。\nいくつか紹介しますので、興味があれば学習してみてください。 watch(リアクティブデータの変化を監視して、何か処理を実行する)ライフサイクルフック(コンポーネントの状態に応じた処理のタイミング)Composables(ロジックや状態を関数として切り出し、複数コンポーネントで再利用する仕組み)Vue Router(画面遷移の管理)Vuex / Pinia(状態管理ライブラリ)カスタムディレクティブ(独自のDOM操作機能を作る) これらの機能は、より複雑で実践的なWebアプリケーション作りに役立ちます。\nVue.jsの基礎が固まったら、少しずつ広げていくのがおすすめです。 引き続き楽しくVue.jsを学んでいきましょう! html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .scnC2, html code.shiki .scnC2{--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .sbEgt, html code.shiki .sbEgt{--shiki-default:#999999;--shiki-default-font-style:italic;--shiki-dark:#666666;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .sHLBJ, html code.shiki .sHLBJ{--shiki-default:#998418;--shiki-dark:#B8A965}",["Reactive",761],{"$si18n:cached-locale-configs":762,"$si18n:resolved-locale":767,"$scolor-mode":768,"$snuxt-seo-utils:routeRules":770,"$ssite-config":771},{"en":763,"ja":765},{"fallbacks":764,"cacheable":27},[],{"fallbacks":766,"cacheable":27},[],"ja",{"preference":769,"value":769,"unknown":27,"forced":199},"system",{"head":-1,"seoMeta":-1},{"_priority":772,"currentLocale":777,"defaultLocale":777,"env":778,"name":779,"url":780},{"name":773,"env":774,"url":775,"defaultLocale":776,"currentLocale":776},-10,-15,-3,-2,"en","production","learn.nuxt.com","https://learn.nuxt.com",["Set"],["ShallowReactive",783],{"search-sections":-1,"ja-/ja/workspace":-1,"ja-navigation":-1,"ja-/ja/workspace-surroundings":-1},{"guide":785,"playground":794,"preview":802,"ui":808,"commands":819},{"features":786,"currentGuide":788,"showingSolution":790,"embeddedDocs":792},["Ref",787],{"fileTree":199,"terminal":199,"navigation":27,"download":27},["EmptyShallowRef",789],"_",["EmptyRef",791],"false",["EmptyRef",793],"\"\"",{"webcontainer":795,"status":796,"error":798,"currentProcess":799,"files":800,"fileSelected":801},["EmptyShallowRef",789],["Ref",797],"init",["EmptyShallowRef",789],["EmptyShallowRef",789],["Map"],["EmptyShallowRef",789],{"clientInfo":803,"location":804,"url":807},["EmptyRef",789],["Ref",805],["Reactive",806],{"origin":344,"fullPath":344},["EmptyRef",793],{"isPanelDragging":809,"isContentDropdownShown":810,"panelDocs":811,"panelEditor":813,"panelPreview":815,"panelFileTree":816,"showTerminal":818},["EmptyRef",791],["EmptyRef",791],["Ref",812],40,["Ref",814],60,["Ref",812],["EmptyRef",817],"0",["Ref",27],{"search":820,"isShown":821,"commandsAll":822},["EmptyRef",793],["EmptyRef",791],["Set"]]