Vue.js
vite
vue
typescript
Docker
を使ったTodoアプリ作成のおおまかなセットアップ
1. プロジェクトのディレクトリを作成・移動
[npm create vite@latest]
(Vite プロジェクトの作成)
2. (npm create vite@latest .
とすれば今いるディレクトリにファイルを展開する)
[npm install]
(依存関係をインストール)
3. プロジェクトに以下の構成ファイルがあるはず
・vite.config.ts
・tsconfig.json
・src/main.ts (エントリーポイント)
4. Vue コンポーネントの作成
5. Docker の設定
・Dockerfile を作成
・docker-compose.yml の作成
6. Docker コンテナの起動
[docker-compose build]
[docker-compose up]
[docker compose up --build]
でも可
7. Docker コンテナを終了する
[docker-compose down]
dockerを起動したがポートに接続できない時
以下のように表示される場合
app-1 | VITE v5.4.8 ready in 156 ms
app-1 |
app-1 | ➜ Local: http://127.0.0.1:5173/
app-1 | ➜ Network: use --host to expose
Network: use --host to expose
-> Vite サーバーが外部アクセスを許可していないことを示している。
解決方法の一つとして
vite.config.tsの設定を編集する
例)
export default defineConfig({
plugins: [vue()],
server: {
host: true, // または `host: '0.0.0.0'` これにより外部アクセスを許可
port: 8080, // コンテナ内でのポート番号
},
})
「Vue.jsとは」
・Javascriptフレームワーク
・単一ファイルコンポーネントで、HTML、CSS、Javascriptが同一のファイルで管理できる
・Axios等他のJavascriptライブラリが導入できる
・高機能なシングルページアプリケーション(SPA)を構築することができる
・vue-routerやvue-validation,vuex等組み合わせることで、大規模なWebアプリ開発までサポートする
「コンポーネントとは」
UI(ユーザーインターフェース)を作成する要素がまとまったもの。HTML、データ、ロジック、CSSを要素としてコンポーネントに含むことができ、そのコンポーネントを組み合わせることで、Webアプリケーションを作成することができる。作成したコンポーネントには名前が付けられ、その名前を登録し呼び出すことで、必要な時に何度でもアプリケーション内で使用することができる。
「Vite(ヴィート)とは」
フロントエンドビルドツール。
・ViteはESモジュールを利用して、依存関係を事前にバンドルし、開発サーバーの起動を非常に高速に行う
・変更を即時に反映するホットモジュールリプレースメント (HMR) 機能により、開発中のフィードバックが迅速。変更されたモジュールのみを更新するため、ページ全体をリロードすることなく、部分的に更新が行われる
・TypeScript、Vue、React、Svelte などさまざまなフレームワークと簡単に統合できる公式プラグインが用意されている。 ー> だからTypeScriptとVueを組み合わせられる。
・
「ref と Ref とは?」
ref:VueのComposition APIで提供される関数で、値を「リアクティブ(再活動的)」にするために使用される。リアクティブな値は、Vueコンポーネント内でその値が変更された際に、自動的にUIを更新する仕組みを持っている。
Ref:refが返すオブジェクトの型を定義するためのTypeScriptの型。Ref<T>はrefによって返される「リアクティブなオブジェクト」を型安全に取り扱うために使う。
[import { ref } from 'vue']
refをvueからインポートし、リアクティブな変数を作成できるようにする。
[import type { Ref } from 'vue']
Refを型定義としてインポートし、refを使った変数の型を指定するために用意している。
[const year: Ref<string | number> = ref('2020')]
ここでyearというリアクティブな変数を作成している。
Ref<string | number>は、この変数がstringまたはnumber型の値を持つことを意味している。
ref('2020')で、初期値として文字列 '2020' を指定している。これはstring型。
[year.value = 2020]
//ok
yearはリアクティブな変数なので、実際の値には.valueを使ってアクセスおよび代入を行う。
[const year = ref<string | number>('2020')]
もしくは、ref() を呼ぶ時に型引数を渡すことで、推論された型を上書きできる
[const n = ref<number>()]
// 推論された型: Ref<number | undefined>
もし、型引数を指定して初期値を省略した場合には、型は undefined を含む union 型になる
コンポーネントのテンプレート内で ref にアクセスするには、下記に示すように、コンポーネントの setup() 関数で宣言し、それを返す
import { ref } from 'vue'
export default {
// `setup` は、Composition API 専用の特別なフック。
setup() {
const count = ref(0)
// ref をテンプレートに公開します
return {
count
}
}
}
<div>{{ count }}</div>
テンプレート内で ref を使用する際、.value をつける必要はない
setup() で状態やメソッドを手動で公開するのは冗長になりがち。単一ファイルコンポーネント(SFC) を使用すれば、これを避けられる。<script setup> によって使い方を簡略化できる。
<script setup> で宣言されたトップレベルのインポート、変数、関数は、同じコンポーネントのテンプレートで自動的に使用可能。テンプレートは同じスコープで宣言された JavaScript の関数と同じだと考えれば、当然ながら、一緒に宣言されたすべてのものにアクセスできる。
[宣言的レンダリングとは]
プログラムの状態(データ)がどのように表示されるかを明確に定義し、UI(ユーザインターフェース)をそのデータの状態に基づいて自動的に更新するプログラミング手法。このアプローチでは、データの変化に応じて画面が自動的に再描画され、開発者は「どのように」UIを更新するかを手動で定義する必要がない。
従来の命令型レンダリング
ー> データが変更されたときに、その変更をUIに反映するための具体的な手順(DOM操作やイベントハンドラ)を実装する必要がある
「reactive()とは」
リアクティブな状態を宣言する方法として、reactive() という API を使う方法もある。内側の値を特別なオブジェクトでラップする ref とは異なり、reactive() はオブジェクト自体をリアクティブにする。
import { reactive } from 'vue'
const state = reactive({ count: 0 })
「reactive() の型付け」
reactive() も引数から暗黙に型を推論する。
const book = reactive({ title: 'Vue 3 Guide' })
// 推論された型: { title: string }
reactive のプロパティを明示的に型付けするには、インターフェースが使える
interface Book {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue 3 Guide' })
「reactive() と ref() の使い分け」
ref() を使う場合: 単一のプリミティブな値や単一のリアクティブなプロパティを管理したいとき。
シンプルな数値、文字列、Boolean などをリアクティブにしたいとき。
データがオブジェクトや配列、ネストされたデータである場合は reactive() を使用する。
reactive() を使う場合: オブジェクトや配列、ネストされたデータ構造をリアクティブに扱いたいとき。
例えば、フォームデータや複雑な状態管理が必要な場合など。
単一の値(文字列、数値、真偽値)や、シンプルなリアクティブな変数が必要な場合は ref() を使用する。
ref() は単一の値またはオブジェクト全体を包み込み、.value プロパティが更新されたときにリアクティブな変更を通知する。
reactive() は Vue.js の内部で Proxy を使ってオブジェクト全体を監視し、プロパティごとの変更を追跡する。
「マスタッシュ構文」
データバインディングで最も基本の形式は、「マスタッシュ構文」(二重中括弧)によるテキスト展開
<span>Message: {{ msg }}</span>
このマスタッシュのタグの中身は、対応するコンポーネントのインスタンスが持つ msg というプロパティの値に置き換えられる。msg プロパティが変更されるたびに、マスタッシュの中身も更新される。
「ディレクティブ」
ディレクティブは v- から始まる特別な属性。これは Vue のテンプレート構文の一部。テキスト補間と同様に、ディレクティブの値はコンポーネントの状態にアクセスできる JavaScript 式。
ディレクティブはレンダリングされる DOM に、特別なリアクティブな振る舞いを割り当てる。
「v-html」
マスタッシュの中では、データが HTML ではなくプレーンテキストとして解釈される。本来の HTML を出力したい場合は、次のように v-html ディレクティブを用いる。
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
Using text interpolation: <span style="color: red">This should be red.</span>
Using v-html directive: This should be red.
「v-bind」
HTML 属性の中ではマスタッシュ構文が使えない。代わりに、以下の v-bind ディレクティブを使用する。
<div v-bind:id="dynamicId"></div>
この v-bind ディレクティブは、要素の id という属性を、コンポーネントが持つ dynamicId というプロパティと同期させるよう Vue に指示している。バインドされた値が null または undefined の場合、その属性はレンダリングされる要素から除外される。
例)
<script setup>
const titleClass = ref('title')
</script>
<template>
<h1 v-bind:class="titleClass">Make me red</h1>
</template>
<style>
.title {
color: black; <- ここをredなど好きに変更できる。
}
</style>
v-bind は使用頻度が非常に高いため、以下の専用の省略記法がある
<div :id="dynamicId"></div>
「v-on」
v-on ディレクティブを使うことで DOM イベントを購読することができる
<button v-on:click="increment">{{ count }}</button>
頻繁に使われるので、v-on には省略記法がある
<button @click="increment">{{ count }}</button>
<script setup>
function increment() {
count.value++ // コンポーネントの状態を更新する ー> 再レンダリングされる
}
</script>
「インラインハンドラー」
インラインハンドラーは、通常、次のような単純なケースで使用される
<button @click="count++">Add 1</button>
「v-model」
v-model は、Vue.js の双方向データバインディングを行うためのディレクティブ。フォーム要素(例えば、<input>, <textarea>, <select> など)と Vue のデータオブジェクトをリンクさせることで、ユーザー入力とデータの同期を簡単に行うことができる。
内部的にはv-bind と v-on を一緒に使うのと同様(これだと面倒で複雑)
<input
:value="text"
@input="event => text = event.target.value">
v-model ー> イベントハンドラー関数を使う必要がない
仕様例)
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
<template>
<input v-model="text" placeholder="Type here">
<p>{{ text }}</p>
</template>
「v-if」
要素を条件付きでレンダリングする際に v-if ディレクティブを使用することができる
<h1 v-if="awesome">Vue is awesome!</h1>
この <h1> は awesome の値が truthy である場合にのみレンダリングされる。もし awesome の値が falsy に変わったら、この <h1> は DOM から削除される。
toggleボタンがクリックされる度に出力が切り替わる実装例)
<script setup>
import { ref } from 'vue'
const awesome = ref(true)
function toggle() {
awesome.value = !awesome.value
}
</script>
<template>
<button @click="toggle">Toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</template>
「v-for」
配列を基にした要素のリストをレンダリングすることができる。
v-for ディレクティブでは、item in items という形式の特別な構文が必要になる。ここで、items は元のデータの配列を指し、item は反復処理の対象となっている配列要素を指す。
<script setup>
import { ref } from 'vue';
const fruits = ref(['リンゴ', 'バナナ', 'オレンジ']); // データとしてフルーツのリストを用意
</script>
<template>
<div>
<h1>フルーツリスト</h1>
<ul>
<li v-for="(fruit, index) in fruits" :key="index">{{ index + 1 }}: {{ fruit }}</li>
</ul>
</div>
</template>
fruit in fruits: fruits 配列内の要素を1つずつ取り出し、fruit として各 li 要素内に表示してる。
:key="index": key 属性を使って各要素に一意の識別子を与えることで、Vue が効率的にリストを管理できる。再レンダリングやリストの更新時に無駄な描画が抑えられる。
v-for の構文
<li v-for="(item, index) in items" :key="index">{{ index }}: {{ item }}</li>
「コンポーネントのレンダリング」
親コンポーネントは、そのテンプレートにある別のコンポーネントを子コンポーネントとしてレンダリングすることができる。子コンポーネントを使用するには、まずそれをインポートする必要がある。
import ChildComp from './ChildComp.vue'
そして、そのコンポーネントをテンプレート内で次のように使用することができる。
<ChildComp />
例)
App.vueファイル
<script setup>
import ChildComp from './ChildComp.vue'
</script>
<template>
<ChildComp />
</template>
ChildComp.vueファイル
<template>
<h2>A Child Component!</h2>
</template>
「propsについて」
子コンポーネントは、親コンポーネントから props を介して入力を受け取ることができる。
まず、受け入れる props を宣言する必要がある。
ChildComp.vueファイル
<script setup>
const props = defineProps({
msg: String
})
</script>
defineProps() はコンパイル時マクロで、インポートする必要ない。一度宣言すると、msg props は子コンポーネントのテンプレートで使用することができる。また defineProps() の返すオブジェクトを介して、JavaScript でアクセスすることができる。
親は属性と同じように、props を子に渡すことができる。動的な値を渡すには、v-bind という構文も使える。
<ChildComp :msg="greeting" />
「onMounted()とは」
Vue.js 3 における Composition API のライフサイクルフックの一つ。このフックは、コンポーネントが DOM に完全にマウントされた後に実行される処理を定義する際に使用される。
「DOM のマウントとは」
以下の段階を指す。
1.テンプレートの解析
Vue コンポーネントのテンプレートが解析され、仮想 DOM が生成されます。
2.仮想 DOM の構築と更新
仮想 DOM (Virtual DOM) は、実際の HTML 要素の簡易版で、Vue 内部で JavaScript オブジェクトとして管理されます。テンプレートからこの仮想 DOM が構築されます。
3.仮想 DOM を実際の DOM に反映
仮想 DOM の内容が解析され、実際の DOM ツリーに変換されます。この段階で、HTML 要素がブラウザのメモリ上に作成され、表示の準備が整います。
4.DOM の挿入
実際の DOM が生成されると、Vue はその DOM を画面(ブラウザ)上に挿入します。この時点で、コンポーネントはブラウザ上に表示され、ユーザーが目視できる状態になります。
「完全にマウントされた後」の具体的な例
DOM 要素の高さや幅を取得
onMounted() の前(例:beforeCreate、created)では、まだ実際の DOM が存在しないため、DOM 要素のプロパティを取得することができない。onMounted() 以降であれば、document.getElementById() や ref を使用して要素を参照し、高さや幅などの情報を取得できる。
「propsとは」
子コンポーネントは、親コンポーネントから props を介してデータを受け取ることができる。
まず、受け入れる props を宣言する必要がある。
例)
<script setup lang="ts">
const props = defineProps<{ // Props としてボタンのlabelを受け取る
label: string;
}>();
defineProps() はコンパイル時マクロで、インポートする必要はない。一度宣言すると、label props は子コンポーネントのテンプレートで使用することができる。
例)
<template>
<button @click="handleClick">{{ props.label }}</button>
</template>
「slotとは」
slot は コンポーネントのテンプレートをカスタマイズするための仕組みで、親コンポーネントから子コンポーネントへ任意の内容を挿入することができる。これにより、再利用可能なコンポーネントを作成しやすくなり、コンポーネントの柔軟性を高めることができます。
<!-- ChildComponent.vue -->
<template>
<div class="child">
<slot>デフォルトの内容</slot>
</div>
</template>
上記のように、<slot> タグを子コンポーネント (ChildComponent.vue) に記述しておくと、親コンポーネントが子コンポーネントを使用する際に、その slot の部分に任意の内容を挿入できる。
<!-- ParentComponent.vue -->
<template>
<div class="parent">
<ChildComponent>
<p>親コンポーネントから渡した内容</p>
</ChildComponent>
</div>
</template>
この場合、ChildComponent の <slot> タグの部分に <p>親コンポーネントから渡した内容</p> が挿入され、最終的に以下のような HTML がレンダリングされる。
<div class="child">
<p>親コンポーネントから渡した内容</p>
</div>
もし、ParentComponent.vue で ChildComponent の間に何も記述しなかった場合、slot 内のデフォルトの内容(この例では「デフォルトの内容」)が表示される。
プロジェクトに応じてカスタマイズでき、汎用性が高まる。
「defineEmits() とは」
子コンポーネントから親コンポーネントにイベントを送るために使用されるメソッド。これにより、子コンポーネントから親コンポーネントへデータを伝えることができ、コンポーネント間のやり取りを簡単に行える。
1. 子コンポーネントでイベントを送信する
例)
<ButtonComponent.vue>
const emits = defineEmits(['click']);
const handleClick = () => {
emits('click'); // クリックイベントが発生したときに親コンポーネントに伝える
};
<template>
<button @click="handleClick">{{ props.label }}</button>
</template>
2. 親コンポーネントでイベントを受け取る
例)
<ButtonComponent label="削除" @click="deleteTask(index)" />
子コンポーネントは以下のように書くことも可
<script>
defineEmits(['click']);
</script>
<template>
<button @click="$emit('click')">{{ props.label }}</button>
</template>