こんにちは。
アイレットデザイン事業部、フロントエンドエンジニアの辻と申します。

Composition APIになり、よりJavaScriptらしく書けるようになったと噂のVue。
チュートリアルを少し触ってみましたので、今回はそちらをまとめてみます。

プロジェクト作成

npm create vue@latest
プロジェクト名などの情報を入力します。
デフォルトでよければ、全てEnterでOKです。

作ったプロジェクトのディレクトリに移動
cd ●●【上で入力したプロジェクト名】

モジュールのインストール
npm install

開発サーバー立ち上げる
npm run dev

これで開発環境ができあがりです。簡単ですね。
これだけだと、開発用ファイルしか存在しないのでサーバーにアップロードする際はビルドが必要です。

ビルドする
npm run build

ディレクトリ構造

・public
画像やCSSなど、外部ファイル群を入れます。

・src
開発フォルダ。開発はこの中のファイルを触っていきます。

ルール

いわゆる、おまじないがいくつかあるので忘れずに記述します。

・scriptタグ
setup属性追加することでComposition APIが使用可能になります。

<script setup>
    ・・・
</script>

・templateタグ
HTML部分はtemplateタグで囲みます。

・ref
リアクティブな変数を定義する時に使います。
リアクティブとは 値が監視され変更が検知される状態のこと で、値が変更されたときに何かしらの処理のトリガーにする時に使用します。
refで宣言した変数をscript内で扱う場合は、変数名に.valueを付けて参照します。

では、次から実際の記述を見ていきます。

v-text, v-html

テキストやHTMLを表示します。
strongタグを含む文字列の変数を表示してみます。

<script setup>
const message = "ようこそ <strong>太郎</strong>さん";
</script>

<template>
  <p v-text="message"></p>
  <p v-html="message"></p>
</template>

結果がこちら。

v-textstrongタグをテキストとして扱い
v-htmlstrongタグをHTMLとして扱います。
またv-text{{ message }}(マスタッシュ構文)で代替が可能です。

<p>{{ message }}</p>

v-bind

属性内に変数を使うときに使用します。
style属性の値に変数を使ってみます。

<script setup>
const message = "ようこそ <strong>太郎</strong>さん";
const color1 = "red";
</script>

<template>
  <p v-html="message" v-bind:style="{ backgroundColor: color1, color: 'white' }"></p>
</template>

結果がこちら。

style属性の値に使用したcolor1が反映されています。
またv-bind::で省略が可能です。

<p v-html="message" :style="{ backgroundColor: color1, color: 'white' }"></p>

v-model

type="text"inputに使用し、データとフロント両方で値をバインドします。
inputを使って、双方向バインディングでフロントに表示してみます。

<script setup>
import { ref } from "vue";

const myname = ref("");
</script>

<template>
  <p>お名前は?: <input type="text" v-model="myname" /></p>
  <p v-html="myname"></p>
</template>

結果がこちら。

inputに入力した内容が反映されています。

v-modelで双方向バインディングされた値を変数内で扱いたい場合は
computedと組み合わせて使用します。

<script setup>
import { ref, computed } from "vue";

const message = computed(() => {
  return "ようこそ<strong>" + myname.value + "</strong>さん";
});
const myname = ref("");
</script>

<template>
  <p>お名前は?: <input type="text" v-model="myname" /></p>
  <p v-html="message"></p>
</template>

結果がこちら。

v-on

イベントをハンドリングします。
簡単なカウンターを作ってみます。

<script setup>
import { ref } from "vue";

const number = ref(0);
const add = () => {
  number.value++;
};
const remove = () => {
  number.value--;
};
</script>
<template>
  <input type="text" v-model="number" />
  <button v-on:click="add()">足す</button>
  <button v-on:click="remove()">引く</button>
</template>

結果がこちら。

足す引くのイベントをそれぞれハンドリングできました。
またv-on:@で省略が可能です。

<button @click="add()">足す</button>
<button @click="remove()">引く</button>

v-for

配列に基づいて繰り返しレンダリングします。
簡単な配列をフロントに表示してみます。

<script setup>
import { ref } from "vue";

const todos = ref(["掃除", "洗濯", "買い物"]);
</script>
<template>
  <ul>
    <li v-for="(todo, i) in todos" :key="i">{{ i + 1 }}:{{ todo }}</li>
  </ul>
</template>

結果がこちら。

配列項目の掃除、洗濯、買い物を表示できました。

v-if, v-else

条件で表示非表示を切り替えます。
青信号赤信号でテキストが切り替わるボタンを作ってみます。

<script setup>
import { ref } from "vue";

const trafficSignal = ref("blue");
</script>
<template>
  <p v-if="trafficSignal === 'blue'">GO!</p>
  <p v-else-if="trafficSignal === 'red'">STOP!</p>
  <button @click="trafficSignal = 'blue'">青信号</button>
  <button @click="trafficSignal = 'red'">赤信号</button>
</template>

結果がこちら。

青信号でGO!が、赤信号でSTOP!が表示できました。

props, slot

コンポーネント間でデータを渡します。

・props
親コンポーネントから子コンポーネントへ値を渡します。

・slot
親コンポーネントから子コンポーネントへテンプレートフラグメントを渡します。
子コンポーネントでデフォルトの値も設定できます。

・AppHeader.vue

<script setup>
defineProps(["textColor"]);
</script>

<template>
  <header>
    <h1 :style="{ color: textColor }">
      <slot>slotデフォルト値</slot>
    </h1>
  </header>
</template>

・App.vue

<script setup>
import AppHeader from "./components/AppHeader.vue";
</script>

<template>
  <AppHeader textColor="red">header</AppHeader>
  <main>contents</main>
</template>

結果がこちら。

親コンポーネント(App.vue)から子コンポーネント(AppHeader.vue)へpropsとslotが渡され
問題なく、反映されました。

ToDoアプリを作ってみる

ここまでの記述を使用してチュートリアルの定番、簡単なToDoアプリを作ってみます。

機能は

  • ToDoの登録
  • ToDoの削除
<script setup>
import { ref } from "vue";

const todos = ref([]);
const newTodo = ref("");

const addTodo = () => {
  todos.value.push(newTodo.value);
  newTodo.value = "";
};

const removeTodo = (index) => {
  todos.value.splice(index, 1);
};
</script>

<template>
  <input type="text" v-model="newTodo" />
  <button @click="addTodo()">追加</button>
  <ul v-if="todos.length > 0">
    <li v-for="(todo, i) in todos" :key="i">
      {{ todo }}
      <button @click="removeTodo(i)" style="cursor: pointer">x</button>
    </li>
  </ul>
  <p v-else>ToDoを追加してください</p>
</template>

結果がこちら。

部分ごとに解説していきます。

const todos = ref([]);
const newTodo = ref("");

上記は

  • ToDoの項目を入れていくための空配列
  • 新しいToDoを入れるための空文字列

を宣言しています。

const addTodo = () => {
  todos.value.push(newTodo.value);
  newTodo.value = "";
};

const removeTodo = (index) => {
  todos.value.splice(index, 1);
};

上記は
ToDoを追加、削除する関数を宣言しています。
addTodo()newTodoに入力した項目をtodosの配列に追加後、newTodoを空にし
removeTodo()splice()で順番を引数に取り、todosから指定の項目を削除します。

<input type="text" v-model="newTodo" />
<button @click="addTodo()">追加</button>

上記は
inputに新しいToDoを入力し、宣言しておいたnewTodoを双方向バインドし
buttonにはaddTodo()をイベントハンドリングしています。

<ul v-if="todos.length > 0">
  <li v-for="(todo, i) in todos" :key="i">
    {{ todo }}
    <button @click="removeTodo(i)" style="cursor: pointer">x</button>
  </li>
</ul>
<p v-else>ToDoを追加してください</p>

上記は
todosに情報が入っていればv-foriを引数にとってリストをレンダリングし
それを使用してremoveTodo()を処理します。
todosに情報がなければToDoを追加してくださいと表示させます。

まとめ

今回は基本的なところを調査してみました。
JavaScriptをある程度理解していれば、ここまでの内容はそんなに難しくないと感じました。
まだまだ機能はあるので、継続して学習していきます。