Vue.js備忘録 (その1)

Vue.jsはユーザーインターフェースを構築するためのJavaScriptフレームワークです
Vue.jsではデータ(data)の値が変化すると、変化した内容が即座にDOMに反映されます(リアクティブシステム
HTMLベースのテンプレート構文を使用しています(オプションのJSXサポートを使用してレンダリング関数を直接作成もできます)

「Vue CLI」を使用して開発環境を一気に作成したり
「CDN」を使用して、jQueryのようにHTML内で手軽に利用できます

「CDN」を使用
HTML内に一行追加するだけで、利用できます

<!-- Vue 2 開発バージョン、便利なコンソールの警告が含まれています -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- Vue 2 本番バージョン、サイズと速度のために最適化されています -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

<!--Vue 3 -->
<script src="https://unpkg.com/vue@next"></script>

本番環境では、新しいバージョンによる意図しない不具合を避けるため、特定のバージョン番号とビルド番号にリンクすることをおすすめします

https://v3.ja.vuejs.org/guide/installation.html
目次
  1. Vueで制御する要素
  2. オプション(Options API)
    1. Dataオプション
    2. ライフサイクルフックオプション
  3. ディレクティブ
    1. v-bind
    2. v-model
    3. v-showとv-if
      1. transition
    4. v-for
    5. v-on
    6. その他のディレクティブ
    7. テンプレートでJavaScript式を活用する
    8. ディレクティブの応用(TODOリスト)
  4. コンポーネント
    1. グローバル登録
    2. ローカル登録
    3. データの受け渡し
      1. 親から子への受け渡し
      2. 子から親への受け渡し
      3. コンポーネントでv-modelを使う
    4. スロット(slot)
  5. Vue CLIでプロジェクト作成

Vueで制御する要素

久しぶりにちょっとVue jsを触ってみたら、「Vue 2」 と 「Vue 3」で「Vueインスタンスの作り方が変わっていたり」「dataの設定が関数のみになっていたり」💦

Vue 2

<!-- id="app"のdiv要素内をVue.jsが制御します -->
<div id="app">
   <p>{{ message }}</p>
</div>

<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello'
}
})
</script>

Vue 3

テンプレート構文{{}}を使って「データ(data)」をDOMに描画します
*データと描画が同期されます(テータバインディング)
Vue.createApp({})オプションを渡してVueインスタンスを作成、ルート要素にマウントします
createApp()メソッドはアプリケーションプログラムの実行に必要な各種情報を提供するインスタンスを返します
mount()の引数に与えられたDOM要素のinnerHTML は、アプリケーションのルートコンポーネントのレンダリングされたテンプレートに置き換えられます

<!-- id="app"のdiv要素内でVue.jsが使えます -->
<div id="app">
   <p>{{ message }}</p>
</div>

<script>
const app = Vue.createApp({
//dataオプション
  data: () => ({
    message: 'Hello'
  })
})
//マウントする
app.mount('#app')
</script>

{{ message }}

オプション(Options API)

Options APIには、Data・DOM・ライフサイクルフック・アセット・Composition・その他のセクションがあります

Dataオプション

よく利用するDataオプションについて

  • dataオプション:Vueアプリケーション内で使用するデータを登録します(リアクティブに動作するデータはdataオプション内に宣言します)
  • methodsオプション:コンポーネントのインスタンスにメソッド(関数)を追加する時に使います
    *テンプレートで呼び出される関数は、コンポーネントが更新されるたびに呼び出されるため、データの変更や非同期操作のトリガーなどの副作用はありません
  • computedオプション(算出プロパティ):dataの値を加工した動的なプロパティを返す(return)ときに利用します(テンプレート内の式のロジックが複雑なときや、再利用する場合はcomputedを利用します)
  • watchオプション(監視プロパティ):特定のデータやcomputedの変更を監視して、変更時に設定した関数を実行します(非同期処理やAjaxなどの重い処理に利用)

methods・watchでアロー関数はNGです
*オブジェクトの中でアロー関数を使うと「this」がVueインスタンスではなくwindowオブジェクトです
data・computedでアロー関数を使う場合は、thisはコンポーネントのインスタンスになりませんが、関数の第1引数でインスタンスにアクセスできます (app)=>app.hoge

「Vue 3」では、dataオプションの設定は関数のみです

//dataにアロー関数を使わなかった場合
data: function() {
 return {
 message: 'Hello'
    }
 } 
//省略した書き方
data() {
 return {
 message: 'Hello'
    }
 } 

//数値・配列・オブジェクトの例
data: () => ({
    message: 'Hello',
    count: 10,
    user: {
      name: '花子',
      age: 20,
    },
    colors: ['Red', 'Green', 'Yellow']
  })
})

*備考:「Vue 2」でもコンポーネントのdataオプションは関数です

computedとmethodsとの違い

  • テンプレート内でmethodsの実行は{{メソッド名()}}
    computedの実行は{{プロパティ名}}です
    computedはreturnで値を返します
  • computedはキャッシュされるので、変更があった時だけ再実行されます
    methodsは変更の有無に関係なくレンダリングのたびに実行されます
  • computedは必要に応じてsetter関数(set(newValue))を設定することができます

watchの基本的な使い方
*関数の引数に設定することで、変更後と変更前の値を取得できます

watch: {
 監視対象: function (newVal, oldVal) {
    //実行したい内容
    },
  }
});

watchとcomputedの比較
例:firstNameとlastNameを合わせてfullNameを表示します

//watchで書きた時
data: () => ({
    firstName: '',
    lastName: '',
    fullName: ''
  }),
  watch: {
    firstName: function(value) {
      this.fullName = value + ' ' + this.lastName
    },
    lastName: function(value) {
      this.fullName = this.firstName + ' ' + value
    }
  }
})
//computedで書いた時
data: () => ({
    firstName: '',
    lastName: ''
  }),
computed: {
    fullName: function() {
      return this.firstName + ' ' + this.lastName
    }
  }
})

watchで、オブジェクトをネストの深さに関係なく監視する場合には、handlerオプションでウォッチャーの本体を指定し、deepオプション「true」を指定します

watch: {
    プロパティ名: {
      handler: function() {
         // プロパティ名のオブジェクトが変更された時の処理をここに記述
      },
      deep: true
   }	
 }

ライフサイクルフックオプション

ライフサイクルフックは、インスタンスが生成されてから、破棄されるまでの間の特定のタイミングで実行できる関数です
*ライフサイクルメソッドの定義にアロー関数は使いません

よく利用するライフサイクルフックオプション

  • beforeCreate:インスタンスが初期化された直後、dataはundefinedの状態
  • created:インスタンスの初期化後で、dataにアクセスできますが、マウントをまだです
  • beforeMount :コンパイル直後で、最初のレンダリングが行われる直前です
  • mounted:マウント後に実行され、DOMにアクセスできます
    *すべての子コンポーネントもマウントされていることは保証されていません

全体がレンダリングされた後に実行したい場合

mounted() {
  this.$nextTick(function () {
    // レンダリング後に実行したいコード
  })
}

ディレクティブ

ディレクティブは、Vue.jsの特別な属性で、「v- 」がついています
HTML要素にディレクティブを書くことで、データの紐付けや表示の制御、イベントへの紐付けなどができ、コードがシンプルです

v-bind

v-bindHTML要素の属性(class・style・value属性などetc)を更新します
HTML属性の内部で{{}}は使えません
代わりに、v-bindを使用します

v-bind:属性名="属性値を表す値または式"
//省略記法
:属性名="属性値を表す値または式"

value属性を、テータ(messageプロパティの値)から取得して表示します

<!-- id="app"のdiv要素の中を編集 -->
<div id="app">
    <input type="text" v-bind:value="message">
</div>

v-model

フォーム要素に双方向データバインディングを付与するために、v-modelを使用します(要素のvalue属性を更新)
dataの値変更←→テンプレートの値変更

フォームで入力した値が、即反映されます

<div id="app">
    <input type="text" v-model="message">
   <p>{{ message }}</p>
</div>

{{ message }}

v-modelの修飾子

  • lazy :入力し終わったタイミングでデータを変更します
  • number :入力した値の型を数値にします
  • trim :入力した値から自動で空白を削除します

v-showとv-if

備考:v-elsev-ifまたはv-else-ifの直後にのみ利用でき、v-else-ifv-ifまたはv-else-ifの直後(複数回可)にのみ利用できます

要素の表示・非表示(有無)を切り替えます
v-show:表示・非表示を切り替え、基本的に切り替えの頻度が高い場合に利用します
v-if:要素の存在の有無を切り替えるので、基本的に切り替えの頻度が低い場合に利用します

<div id="app">
  <span v-if="toggle">Hello</span>
</div>
const app = Vue.createApp({
  data: () => ({
//toggleがfalseの時に、Helloは消えます
    toggle: true
  })
})
app.mount('#app')

transition

表示、非表示のトランジション効果を適用もできます
適用したい要素を「transitionタグ」で囲むことでトランジションのタイミングに合わせたCSSのクラスが自動で生成されます
*「transitionタグ」の中は単一要素にする必要があります

<div id="app">
    <button v-on:click="toggle = !toggle">クリック</button>
<!-- 適用したい要素をtransitionタグで囲みます -->
<!-- name="fade" fadeはCSSのプレフィックス(先頭の文字)です -->
    <transition name="fade">
      <p v-show="toggle">Hello</p>
    </transition>
  </div>
const app = Vue.createApp({
  data: () => ({
    toggle: true
  }),
})
app.mount('#app')
/*transitionタグのname属性のプレフィックスfade-がついています*/
.fade-enter-active,
.fade-leave-active {
  transition: opacity 2s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

Hello

https://v3.ja.vuejs.org/から引用

リストのトランジション
追加や削除を行うとその周りにもトランジション効果を適応させるには「transition-groupタグ」を使います
参考:https://v3.ja.vuejs.org/guide/transitions-list.html

v-for

配列内のデータを使って、リストを表示します

<div id="app">
  <ul>
    <li v-for="color in colors">{{ color }}</li>
  </ul>
</div>
const app = Vue.createApp({
  data: () => ({
     colors: ['Red', 'Green', 'Yellow']
  })
})
app.mount('#app')
  • {{ color }}

オブジェクトの場合
(value, key) in で、valueが先です
*オブジェクトの反復処理は、順序の一貫性は保証されていません

<div id="app">
  <ul>
    <li v-for="(value, key) in colors">
      {{ key }}: {{ value }}
    </li>
  </ul>
</div>
const app = Vue.createApp({
 data: () => ({
    colors: {
       red: '赤色',
      green: '緑色',
      yellow: '黄色'
    }
  })
})
app.mount('#app')
  • {{ key }}: {{ value }}

v-forに、一意のkey属性を与えることが推奨されています(要素の再利用と並び替えができるように)

<div v-for="item in items" v-bind:key="item.id">
  <!-- content -->
</div>

v-ifとv-forは同じ要素内で使用しないようにします
条件が必要な時はtemplateタグを使います

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

v-on

イベントリスナー(click・changeなど)のメソッドを呼び出すことができます
*methodsオプションに関数を定義して呼び出すことが多いです

v-on:イベント名="メソッド名や式"
//省略記法
@:イベント名="メソッド名や式"

クリックを押すと、messageの値「Hello」が「Good bye」に変わります

<div id="app">
  <button v-on:click="onClick">クリック</button>
  <p>{{ message }}</p>
</div>
<script>
const app = Vue.createApp({
 data: () => ({
    message: 'Hello'
  }),
//methodsにonClick関数を定義、thisはdataオブジェクトのこと
 methods: {
    onClick: function() {
      this.message='Good bye'
    }
  }
}).mount('#app')
</script>

{{ message }}

その他のディレクティブ

  • v-once: 一度だけレンダリングし、その後は静的コンテンツとして扱われます
  • v-pre{{}}タグそのものを表示するときなど、コンパイルを省略します
  • v-html:HTMLとして挿入されます
    *、XSSの脆弱性があり、ユーザーからの入力表示には使わないこと

描画時に、一瞬「マスタッシュ{{}}」が表示されてしまうのを防止するために、v-cloakディレクティブと、CSSを組み合わせて利用します

<div v-cloak>
  {{ message }}
</div>
[v-cloak] {
  display: none;
}

テンプレートでJavaScript式を活用する

データバインディング内部でJavaScript式を書いたり、関数の呼び出すことができます

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>
<div v-on:click="toggle = !toggle"></div>

<!-- これはNG 文です -->
{{ var a = 1 }}

<!-- これはNG フロー制御はいずれも動作しません 三項演算子を使用します -->
{{ if (ok) { return message } }}

角括弧[]で囲むことで JavaScript式をディレクティブの引数に使うこともできます
eventNameの値が “click” だと、v-on:[eventName]v-on:clickと同じです
* stringかnull以外の値は警告になり、スペースや引用符は使えません

<button v-on:[eventName]=""> ... </button>

ディレクティブの応用(TODOリスト)

<!-- id="app"のdiv要素内がレンダリング対象 -->
<div id="app">
  <!-- .prevent修飾子 イベントでページのリロードを止めるため -->
    <form v-on:submit.prevent>
        <!-- v-modelでデータバインディング -->
      <input type="text" v-model="newItem" />
      <button v-on:click="addItem">追加</button>
    </form>
    <ul>
      <li v-for="(todo, index) in todos">
      <!-- 完了のチェックbox チェックするとisDoneはtrue -->
      <input type="checkbox" v-model="todo.isDone" />
      <!-- 完了済みのスタイルを変更 -->
         <span v-bind:style="{ 'text-decoration': todo.isDone ? 'line-through' : 'none' }">{{ todo.item }}</span>
       <!-- 削除ボタン -->
        <button v-on:click="deleteItem(index)">削除</button>
      </li>
    </ul>
   <!--未完了todoの数 / todoの数を表示 -->
   <p>{{ remaining.length }}/{{ todos.length }}</p>
    <!-- dataオブジェクトを表示 -->
    <pre>{{ $data }}</pre>
  </div>
const app = Vue.createApp({
  data: () => ({
//inputとされる値
    newItem: '',
//todoを管理する配列
todos: [{item:'タスク1', isDone: false} ,{item:'タスク2', isDone: false}]
  //  todos: []
  }),
  methods: {
//todos配列にtodoを追加する関数
    addItem: function(event) {
   //todosの配列に、todoオブジェクトを追加、削除する(完了未完了も一緒に管理する)
    let todo = {
        item: this.newItem,
        isDone: false
    }
   //空の時は追加しない
      if(this.newItem === '') return
      this.todos.push(todo)
   //追加後、inputを空にする
      this.newItem = ''
   },
//todos配列からtodoを削除する関数
    deleteItem: function(index) {
      this.todos.splice(index, 1)
    }
  },
//算出プロパティーで、未完了のtodoを取得
 computed: {
      remaining: function () {
        return this.todos.filter(function (todo) {
          return !todo.isDone;
        });
      }
    }
})
app.mount('#app')
  • {{ todo.item }}

{{ remaining.length }}/{{ todos.length }}

{{ $data }}

コンポーネント

コンポーネントは名前付きの再利用可能なインスタンスです
コンポーネントを登録すると、HTMLタグのように何度でも利用できます

コンポーネントの登録には「グローバル」と「ローカル」があります

DOM テンプレートの中では、コンポーネント名は全て小文字で単語をハイフンを用いて繋げ、閉じタグをつけます
<my-component></my-component>

グローバル登録

グローバルに登録されたコンポーネントは、アプリケーション内のどのテンプレートでも使えます

//componentメソッドでグローバルに登録
Vue.component('コンポーネント名', コンポーネントの定義)

Helloコンポーネントをテンプレートで使います

<div id="app">
   <hello-component></hello-component>
</div>

appオブジェクトと同様にコンポーネントオブジェクトを作成します

const app = Vue.createApp({

})
app.component('hello-component', {
  template: '<p>Hello!</p>'
})
app.mount('#app')

templateは、通常はテンプレートリテラル(``)を使い、単一のルートにする必要があります

ローカル登録

特定の親コンポーネントからのみ利用するコンポーネントを登録します
*ローカル登録が必要な理由は、グローバル登録でWebpackのようなビルドシステムを利用した場合、使用していないコンポーネントもビルドに含まれてしまいファイルサイズが大きくなるためです

 <div id="app">
    <hello-component></hello-component>
 </div>

親コンポーネントのコンポーネントオプションに追加します

//コンポーネントを定義して
const helloComponent = {
  template: `<p>Hello</p>`,
}
const app = Vue.createApp({
//コンポーネントオプションに追加
  components: {
    //キーがコンポーネント名:値が定数名
    // 'hello-component': helloComponent
    //省略記法だけでもOK
      helloComponent
  }
})
app.mount('#app')

データの受け渡し

親から子への受け渡し

props(子側のデータの受け口の役割です)
親側のコンポーネントでは、カスタム属性(プロパティ)を使います
propsオプションは、コンポーネントが受けとるカスタム属性(プロパティ)のリストです
キャメルケース(postTitle)のプロパティは、HTML内ではケバブケース(post-title)にする必要があります

<div id="app">
    <!-- post-title属性で'Hello props!'を渡す -->
    <hello-component post-title="Hello props!"></hello-component>
 </div>
const helloComponent = {
    template: `<p>{{ postTitle }}</p>`,
//コンポーネントの定義にpropsを追加
    props: ['postTitle'],
}
const app = Vue.createApp({
  components: {
    'hello-component': helloComponent
  }
})
app.mount('#app')

propsは、配列ではなくオブジェクトとして列挙し、キーにプロパティ名、値にプロパティの型を設定できます

//配列
props: ['postTitle']
//オブジェクト
props: {
  postTitle: String,
}

*受け取ったデータをリアクティブにするにはpropsで受け取った値をdataや、computedで処理する必要があります

カスタム属性(プロパティ)に変数の値を(JavaScriptの式)を割り当てるにはv-bindを使います

<div id="app">
    <!-- 親のテータの値を動的に渡す -->
    <hello-component v-bind:post-title="parentTitle"></hello-component>
</div>
const helloComponent = {
    template: `<p>{{ postTitle }}</p>`,
    props: ['postTitle'],
}
const app = Vue.createApp({
 data() {
      return{
     //親のデータの値
        parentTitle:'親のタイトル'
       }
    },
  components: {
    'hello-component': helloComponent
  }
})
app.mount('#app')

親から配列を受け取り、リストで表示する

<div id="app">
    <!-- 親の配列のデータを渡す key属性は必須 itemとlistを紐付ける-->
    <color-list
      v-for="list in lists"
        :key="list.color"
        :item="list">
     </color-list>
</div>
const colorList = {
    template: `<ul><li>{{ item.color }}</li></ul>`,
 //props itemはlistsの各オブジェクト
    props: {
        item: {
         type: Object
        }
    },
}
const app = Vue.createApp({
    data() {
        return {
            lists: [
                { color: 'Red' },
                { color: 'Green' },
                { color: 'Yellow' }
            ]
        }
    },
  components: {
    'color-list': colorList
  }
})
app.mount('#app')

データは親から子への単一方向のバインディングです(親のデータが更新された時は子のデータも更新されます)
子コンポーネント内でプロパティの値を変化させてはいけません
では・・・

子から親への受け渡し

子から親にデータを通知するには
子コンポーネントで、 $emit メソッドにイベントの名前を渡してカスタムイベントを発行します(カスタムイベントは特定の値と一緒に発行することもでき、親のメソッドの引数で受け取ることができます)
親は、 v-onでカスタムイベント(子供のイベント)が発生したらメソッドを実行します

<div id="app">
    <!-- 子供のカスタムイベントが発火したら、メソッドを実行します-->
    <emit-test v-on:child-event="parentMethod"></emit-test>
    <p>値:{{childData}}</p>
</div>
const emitTest = {
    template: `<div>
        <button @click="childMethod">子のボタン</button>
        </div>`,
    methods: {
        childMethod: function () {
        //カスタムイベントを登録する 第二引数は渡す値
         this.$emit('child-event', '子の値')
      }
    }
}
const app = Vue.createApp({
    data() {
      return{
           childData: ''
       }
    },
  components: {
    'emit-test': emitTest
    },
//カスタムイベントが発火した時の処理 引数で子供のデータを受け取る
  methods:{
    parentMethod: function (e) {
      this.childData = e
    }
  }
})
app.mount('#app')

値:{{childData}}

コンポーネントでv-modelを使う

v-modelv-onv-bindで書くことができます
*コンポーネントでは「Vue 2」と「Vue3 」で「props」と 「event」のデフォルト名が異なります

<input
  :value="message"
  @input="message = $event.target.value"
/>
<!-- Vue 2 -->
<custom-input
  :value="message"
  @input="message = $event"
/></custom-input>
<!-- Vue 3 -->

<custom-input
  :model-value="message"
  @update:model-value="message = $event"
></custom-input>

「コンポーネントでv-modelを使う(「Vue3 」)

<div id="app">
    <!-- コンポーネントでv-model -->
    <custom-input v-model="searchText">
    </custom-input>
    <p>入力した値:{{ searchText }}</p>
</div>
const customInput = {
    props: {
        modelValue: {
           type: String
       }
    },
    template: `<div>
       <input type="text" v-bind:value="modelValue" v-on:input="childEvent">
        </div>`,
    methods: {
        childEvent: function (e) {
         this.$emit('update:modelValue', e.target.value)
      }
    }
}
const app = Vue.createApp({
    data() {
      return{
           searchText: ''
       }
    },
  components: {
    'custom-input': customInput
    }
})
app.mount('#app')

入力した値:{{ searchText }}

子供のコンポーネントでv-modelを使う場合は、computedでgetter関数とsetter関数を使います

computedでgetter関数とsetter関数を使う

 computed: {
      プロパティ名:{
       get: function () {
        return ・・・
      },
       set: function (newValue) {
        //newValueの処理
      }
      }
    }

子供のコンポーネントでv-modelを使う

<div id="app">
<!-- カスタムイベントで子から入力した値を受け取る -->
    <custom-input v-on:child-event="parentValue =$event">
    </custom-input>
     <p>入力した値:{{ parentValue }}</p>
</div>
const customInput = {
    template: `<div>
       <input type="text" v-model="childText">
        </div>`,
    data() {
      return{
           childValue:''
       }
    },
    computed: {
      childText:{
        get: function(){
            return this.childValue
        },
         //入力された値を受け取る
        set: function(value){
            this.childValue = value
            //カスタムイベントを登録して、値を親に送る
            this.$emit('child-event', value)
        }
      }
    }
}
const app = Vue.createApp({
    data() {
      return{
           parentValue: ''
       }
    },
  components: {
    'custom-input': customInput
    }
})
app.mount('#app')

入力した値:{{ parentValue }}

スロット(slot)

子供のコンポーネントの<slot></slot>の中に、親のコンポーネントの開始タグと終了タグの中身を差し込むことができます

<div id="app">
    <child-component>親のABC</child-component>
</div>
const childComponent = {
    template: `<div>
      <p>ABC</p><slot></slot><p>DEF</p>
        </div>`,
}
const app = Vue.createApp({
  components: {
    'child-component': childComponent
    }
})
app.mount('#app')
親のABC

子に複数のslotがある場合など
slotのname属性に名前をつけ、親側でtemplateタグを使いv-slotの引数に名前を指定し、子のそれぞれのslotに差し込みます
省略記法の例:v-slot:first#first

<div id="app">
    <child-component>
        <!-- v-slotの引数に名前を指定 -->
        <template v-slot:first>最初</template>
        <template v-slot:last>最終</template>
        名前はついていません
    </child-component>
</div>
const childComponent = {
    template: `<div>
      <slot name="first"></slot>
      <slot></slot>
      <slot name="last"></slot>
        </div>`
}
const app = Vue.createApp({
  components: {
    'child-component': childComponent
    }
})
app.mount('#app')
名前はついていません

親側コンポーネントの開始タグと終了タグの間に、子コンポーネントのデータを利用します
子のslotでv-bindを利用してデータとslotを紐付けます
slotに束縛されたプロパティはスロットプロパティ呼ばれ、親側でv-slotに渡されたスロットプロパティ(例:slotProps 名前は自由)を受け取り利用できます

<div id="app">
    <todo-list>
        <!-- v-slotに値を指定して、slotPropsを利用します *引数のdefaultは名前なしのslot-->
        <template v-slot:default="slotProps">
            {{ slotProps.todo }}
        </template>
    </todo-list>
</div>
const todoList = {
    template: ` <ul>
      <li v-for="(item, index) in items">
        <slot v-bind:todo="item"></slot>
      </li> </ul>`,
    data() {
      return {
       items: ['タスク1', 'タスク2', 'タスク3']
    }
  },
}
const app = Vue.createApp({
  components: {
    'todo-list': todoList
    }
})
app.mount('#app')

Vue CLIでプロジェクト作成

「Vue 3」ではIE11サポートは削除

Vue CLIは、Vueのコマンドラインツールです

Vue CLIをインストールしていない場合は、グローバルにインストールします
(Vueコマンドが使えるようなります)
*Vue 3はVue CLIのバージョン4.x以上です

# インストール 
npm install -g @vue/cli
# インストールされていか確認
vue -V

作業ディレクトリに移動してプロジェクトを作成

cd 作業ディレクトリ
vue create プロジェクト名

ウィザードに沿って設定します
*選択は上下キーでカーソルを移動し、エンターキーを押すと次へ進みます
*チェックのON/OFFはスペースキー、決定はエンターキーです

  1. プリセットを選択(登録済みの環境か「Manually select features(手動で設定)」を選択します
  2. 開発環境で必要な機能を選択します(選択した内容で次のウィザードが異なります)
  3. ここまで設定した内容をプリセットとして保存するかを選択します(保存する場合は「y」を入力してエンターし、プリセット名を入力します)

SFC(単一ファイルコンポーネント)
拡張子が.vueのファイル

テンプレート(template)・ロジック(script)・スタイル(style) を 1つのファイルにまとめた特別なファイル形式です
ファイル名はすべてパスカルケース(PascalCase)または、すべてケバブケース (kebab-case)が推奨されています
ファイル名 からコンポーネント名を自動で推測します(PascalCase.vueファイルは、そのテンプレート内で<PascalCase />)

単一ファイルコンポーネントや、文字列テンプレートでは中身のないコンポーネントは自己終了形式で、コンポーネント名はパスカルケース(PascalCase)推奨されています(例:<MyComp />
余談:DOMテンプレートとでは(<my-comp></my-comp>

<template>
<Hoge />
</template>

<script>
import Hoge from './Hoge.vue'
export default {
 name: 'ファイル名と同じ名前'
//テンプレートで利用可能なコンポーネント(importしたコンポーネント)
 components:{ Hoge } 

}
</script>

<style scoped>
/* scopedでスタイルの有効範囲をテンプレート内にします */
</style>

main.jsの比較(エントリーポイント)

// Vue 3 分割代入で必要なメソッドを読み込む
import { createApp } from 'vue'
import App from './App.vue'
//createAppの後に他のメソッドチェーンできます(例:.use(プラグイン))
createApp(App).mount('#app')

// Vue 2.x 以前
import Vue from 'vue'
import App from './App.vue'
//Vueインスタンスを作成
new Vue({
render: h => h(App)
}).mount('#app')