この記事では Vue.js を用いたタスクリスト管理アプリの実装を行いながら、Vue.js の各機能について紹介していきます。
開発環境は はじめてのVue.js - 単一ファイルコンポーネントを作れる環境構築編 (npm + gulp + browserify + babel + vueify)に準じて説明していきますので、未読の方は一度目を通していただけるとありがたいです。
モデルの実装
Vue.js はリアクティブデータバインディングを実現するためのフレームワークで、MVVMパターンで言う所の VM, View に焦点を当てています。
逆に Model 層については特にサポートがあるわけではなく、通常の JavaScript のオブジェクトを使用することになります。
ビジネスロジックやデータ操作自体を Model 層の通常の JavaScript の世界のみで完結できるので、 ロジックと見た目の分離が行いやすく 設計ができるようになります。
まずは Vue と関係のないタスクリストのモデル実装を用意しましょう。 models ディレクトリを作成し、次の2ファイルを作成し、main.js を書き換えてください。
var Task = require('./task').default
/**
* タスクのリスト
*/
export default class TaskList
{
constructor(name = 'default')
{
// コンストラクタで指定された名前をもとに、タスクリストを取得する
var source = TaskList.fetch(name)
this.title = source.title || name
this.tasks = []
let tasks = source.tasks || []
tasks.forEach((task) => {
this.tasks.push(new Task(task))
})
}
// 新しいタスクをタスクリストに追加する
add(task)
{
this.tasks.push(task)
}
// タスクリストを永続化する
save()
{
// 今回はローカルストレージを使います。
localStorage.setItem(TaskList.storage_key(this.title), JSON.stringify(this))
}
static storage_key(name)
{
return 'hello-vuejs-' + name
}
// ローかストレージから保存された内容を復元します
static fetch(name = 'default')
{
return JSON.parse(localStorage.getItem(TaskList.storage_key(name)) || '{}')
}
}
export default class Task
{
constructor(source = {})
{
this.title = source.title || ''
this.estimated_on = source.estimated_on || ''
this.completed_at = source.completed_at || ''
this.memo = source.memo || ''
}
}
var Vue = require('vue')
// global.TaskList に require の内容を入れることで、ブラウザのコンソールから TaskList が使えるようになります。
var TaskList = global.TaskList = require('./model/task_list').default
var Task = global.Task = require('./model/task').default
new Vue({
el: '#app',
components: {
MyMessage: require('./components/message.vue'),
}
})
細かな説明は省略しますが、ブラウザのコンソールにて次のようなプログラムを書き、挙動を確認してみてください。
var list = new TaskList
list.add(new Task({title: "my 1st task"}))
list.add(new Task({title: "my 2ndt task"}))
list.save()
var fetched = new TaskList
list.tasks[0] == fetched.tasks[0] // true
登録されたタスクがブラウザのローカルストレージに保存されているのがわかるかと思います。
大事なのは、このモデル部分の実装は Vue.js と全く関係がない ということです。繰り返しになりますが、Vue.js はリアクティブなデータバインディング、すなわち MVVM パターンで言う所の ViewModel 層をメインで取り扱います。Model 層であるデータモデルやビジネスロジックには一切手を出さないので、この部分は自分で考える必要があります。
(参考: ここまでの作業 https://github.com/ayasuda/hello_vuejs/commit/e1b54133413130d1441207f0c8e82fb46c19886b )
リアクティブなデータバインディング
それでは、早速データバインディングを試してみましょう。
方法はとても単純で、 Vue
のコンストラクタのオプションとしてデータモデルを指定するだけです。
ファイルを次のように書き換えてみましょう
var Vue = require('vue')
var TaskList = require('./model/task_list').default
var Task = require('./model/task').default
// ブラウザのコンソールからもアクセスできるように、 global.task_list を作っておきます
var task_list = global.task_list = new TaskList
new Vue({
el: '#app',
data: task_list, // コンストラクタのオプション `data` に task_list を渡します
components: {
MyMessage: require('./components/message.vue'),
}
})
コンストラクタのオプション data
に JavaScript のオブジェクト、ここでは task_list
を指定することで Vue.js が task_list
のデータの監視を始めます。
正しくバインドされたか試してみましょう。 index.html の <div id="app">
の中を次のように書き換えてみてください。
<div id="app">
<h2>{{title}}</h2>
<my-message></my-message>
</div>
ブラウザでこのファイルにアクセスすると、 task_list.title
の内容が <h2>
内に表示されているのが確認できると思います。
さらにブラウザのコンソールでこんな操作をしてみてください。
task_list.title // "default"
task_list.title = "バインディングされているかな?"
コンソールで変更したタイトルが、すぐに <h2>
内に反映されるのが確認できるかと思います。
Vue.js がバインドされたオブジェクト、task_listの変更を監視しているためです。
バインドされたオブジェクトが変更された際に、Vue.js を通して View の内容が自動的に変更されます。
このリアクティブなデータバインディングの何が良いのでしょうか?
最大のメリットは、Vue.js 以外のソースコードがロジックやデータの変更に集中できることです。
例えばボタンがクリックされたら ajax の通信が走ってデータが更新されるプログラムを書きたいとき、データを更新すれば、Vue.jsを通して自動的にViewが更新される ことになります。
(参考: ここまでの作業 https://github.com/ayasuda/hello_vuejs/commit/fc55b8c0ccb4553fa6fbb1aa6b0ced1aebbcc429)
VM => View のバインディングとしての Mustache 構文
Vue
インスタンスから View へのバインディングとして Mustache 構文が用意されています。
具体的には <h2>{{title}}</h2>
で登場した二重中括弧の構文です。
Vue
インスタンスは MVVM パターンで言う所の ViewModel に相当しており、この ViewModel が管理しているデータが Mustache 構文を通して View 側にひも付けられています。
データバインディング構文についてはガイドのデータバインディング構文を適宜参照頂ければと思います。
コンポーネントツリーとデータの引き継ぎ
Vue.js のもう一つの特徴であるコンポーネントシステムについて、タスクリストの表示を、専門のコンポーネントに移植することで見てきましょう。
ひとまず、タスク用のコンポーネントを用意し、タスクの表示部分を移動してみましょう(ついでに不要となった <my-message>
コンポーネントも消してしまいましょう)。
それぞれのファイルを次のように書き換えてください。また components/message.vue を削除してください。
<!-- vim: set ft=html : -->
<template>
<div>ここにタイトル</div>
</template>
<script>
export default { }
</script>
var Vue = require('vue')
var TaskList = require('./model/task_list').default
var task_list = global.task_list = new TaskList
new Vue({
el: '#app',
data: {
list: task_list,
},
components: {
MyTaskList: require('./components/task_list.vue')
}
})
<div id="app">
<my-task-list></my-task-list>
</div>
コンポーネントの用意はできましたが、まだタイトルは表示できません。親コンポーネントから子コンポーネントにデータを渡さなければいけません。この場合は props
によるデータの伝達を使います。
props を使うと、自作コンポーネントの属性のような形で、親コンポーネントからデータを受け取ることができます。サンプルを見るのが手っ取り早いでしょう。以下のサンプルの msg
が props にあたります。
<my-component msg="ここ親からのデータを指定できる"></my-component>
今回は親コンポーネントの動的な要素 task_list
オブジェクトを子コンポーネントに引き渡したいので、親コンポーネント側からは動的な props のアサインを、子コンポーネント側からは動的な props の受け取り準備を実装する必要があります。
動的な props のアサイン
Vue.js のコンポーネントでは、 props の受け渡し方法として「リテラル」と「動的な要素」の2つのやり方が用意されています。
<my-component props1="リテラルの受け渡し"></my-component>
<my-component :props2="動的な要素の受け渡し(省略記法)"></my-component>
<my-component v-bind:props3="動的な要素の受け渡し"></my-component>
「リテラル」と「動的な要素」の違いは簡単で、受け渡した要素が変更される可能性があるかです。
「リテラル」は変更される可能性のない値に用います。例えば、 <my-custom-img src="ここのパスは動的に変化しない">
時などに用いると良いでしょう。
一方「動的な要素」は動的に変更される可能性のある値に用います。例えば <my-progress-bar :progress="40">
みたいな時に用いると良いのではないでしょうか?
今回渡したい task_list
はコンポーネントの外側から変更される可能性のあるオブジェクトです。よって、動的な props として受け渡してやる必要があります。
動的な props の受け取り
子コンポーネント側でも、動的な props を受け取ることを明示的に宣言する必要があります。
受け取る props の定義は簡単で、コンポーネントオプション props
を記すだけです。
<!-- vim: set ft=html : -->
<template>
<div>ここにタイトル</div>
</template>
<script>
export default {
props: ["list"] // ここで受け取る props を宣言する
}
</script>
単純な props ならばこれで十分なのですが、動的な props としてオブジェクトを受け取る場合には props 検証を用いて、必要条件を明示しておいた方が有利です。次のようにして、 props として受け取れる値に制約をかけることができます。
props に制約をかけつつ、タスクリストのタイトルを list.title
に変更してみましょう。
<!-- vim: set ft=html : -->
<template>
<div>{{list.title}}</div>
</template>
<script>
var TaskList = require('../model/task_list').default
export default {
props: {
// list として受け取れるのは Object と制約をかける。また、指定されない場合のデフォルト値をファクトリ関数を使って定義します。
list: { type: Object, default: () => { return new TaskList } }
}
}
</script>
変更が終わったらブラウザで動きを確認してみてください。
props を使うことで、他の Vue インスタンスを作成した時や、別のアプリなどを作成する際も、 TaskList
インスタンスを受け取るコンポーネントとして独立して振る舞いやすいコードがかけるようになります。
他にも様々な機能を Vue.js は用意しておりますので、 Props 検証 をご一読頂ければと思います。
(参考: ここまでの作業 https://github.com/ayasuda/hello_vuejs/commit/c2f744c161c862805fae1ba33dd601961182970b)
リスト表記と View => VM のバインディングとしてのディレクティブ
タスクリストを専門の <my-task-list>
コンポーネントに押し込めることができました。次に、タスクのリストを表示してみましょう。
リストのレンダリングには v-for
ディレクティブを使います。タスクリストコンポーネントのコードのテンプレート部分を次のように書き換えてみてください。
<template>
<div>{{list.title}}</div>
<ul>
<li v-for="task in list.tasks">
{{task.title}}
</li>
</ul>
</template>
ブラウザで動作を確認してみてください。タスクがリスト形式で表示されているはずです。
ここで用いたディレクティブを一言で言えば、 View から ViewModel へのバインディングと言えます。
例を見てみましょう。
<a href="{{ url }}">some link</a>
<a v-bind:href="url">other link</a>
この両者は全く同じ動きをし、事実内部コードも同じよう処理されます。ViewModel の url という値が変わると、動的にリンク先が変更されるコードです。
ここは著者の考えとなりますが、この2コードは動きは同じですが意味合いのニュアンスに違いがあります。
Mustache 構文は、ViewModel のプロパティやメソッドの値を View にアサインするという意味合いに取れますが、ディレクティブを用いる場合は、View の方から積極的に ViewModel のプロパティやメソッドの値を参照するというニュアンスが生まれます。(多分)
Vue に組み込まれたディレクティブは様々あります。ディレクティブの一覧については API ドキュメントのディレクティブ を、v-for
ディレクティブの使い方については ガイドのリストレンダリング を、そして、独自ディレクティブの作り方については ガイドのカスタムディレクティブの章 をご参照ください。
(参考: ここまでの作業 https://github.com/ayasuda/hello_vuejs/commit/c54171a0e98d31c9ed2d9d4a2e91e98ea7cdaba7 )
コンポーネントを増やす
コンポーネントの中にコンポーネントを入れられるのでしょうか? もちろんできます。
せっかくなのでタスクも表示を行う専門のコンポーネントを作ってしまいましょう。
components/task.vue
を追加し、 components/task_list.vue
を次のように書き換えてみてください。
<template>
<div>{{task.title}}</div>
</template>
<script>
var Task = require('../model/task').default
export default {
props: {
task: { type: Object, default: () => { return new Task } }
}
}
</script>
<template>
<div>{{list.title}}</div>
<ul>
<li v-for="task in list.tasks">
<my-task :task="task"></my-task>
</li>
</ul>
</template>
<script>
var TaskList = require('../model/task_list').default
export default {
props: {
list: { type: Object, default: () => { return new TaskList } }
},
components: {
MyTask: require('./task.vue')
}
}
</script>
コンポーネントを細かく保つのは、一つのコンポーネントで取り扱う関心をなるべく小さくしたいためです。
一般に小さなコンポーネントを大きくまとめるのは、大きなコンポーネントを小さく分割するよりも難しい作業となります。ですので、可能ならば定期的にコンポーネントを切り出すことをお勧めします。
コンポーネントを分割する時に大事な点として、子供コンポーネントが親コンポーネントに依存しないようにすることが重要です。 components/task.vue を読んでいただければわかるかと思いますが、このコンポーネントはあくまでも Task
インスタンスを表示する <div>
を提供しているのみです。
ですので、例えば main.js から直接呼び出すこともできますし、将来他のコンポーネントからも呼び出しやすくなっています。
(参考: ここまでの作業 https://github.com/ayasuda/hello_vuejs/commit/e0fb43e0a360beb8af87a9c46d9fce0fb8632feb)
イベントの取り扱い方
タスクリストの表示はここまでうまくいっています。次はタスクの追加を実装しながら、イベントの取り扱いについてみていきたいと思います。
とはいえ、実のところ全く難しくありません。
イベントの取り扱いには v-on
ディレクティブを使います。基本的な使い方は以下の通りです。
<div v-on:click="someVMmethod">クリックすると何か起きる</div>
基本は v-on
の次に ':' 区切りでイベント名を記し、それを ViewModel のメソッドにバインドするだけです。
早速実装してみましょう。 components/task_list.vue を次のように書き換えてください。
<template>
<div>{{list.title}}</div>
<ul>
<li v-for="task in list.tasks">
<my-task :task="task"></my-task>
</li>
</ul>
<div v-on:click="addTask">タスクを追加する</div>
</template>
<script>
var TaskList = require('../model/task_list').default
export default {
props: {
list: { type: Object, default: () => { return new TaskList } }
},
components: {
MyTask: require('./task.vue')
},
methods: {
addTask: function() {
console.log("タスクを追加するがクリックされた!")
}
}
}
</script>
ブラウザで挙動を確認するとわかると思いますが、期待通り、「タスクを追加する」をクリックすることでコンソールに「タスクを追加するがクリックされた!」が表示されます。
メソッド名については onClickAddUserButton
の様に、操作を表すメソッド名をつける流派と addTask
の様にやることを示すメソッド名をつける流派があると思いますが、レンダリング周りを Vue.js がリアクティブなデータバインディングにて行う以上、やることを示すメソッド名の方が良いでしょう。
メソッドの長さについても考慮が必要です。どこまでが Model 層で行うべき操作で、どこからが ViewModel 層で行うべき操作なのか、意識してプログラミングしていただければと思います。なお、一般的に単体テストしやすいのは Model 側のコードです。
最後に、実際にタスクを追加するコードを追加しましょう。 components/task_list.vue の <script>
部分を次の様に書き換えてください。
var TaskList = require('../model/task_list').default
var Task = require('../model/task').default
export default {
props: {
list: { type: Object, default: () => { return new TaskList } }
},
components: {
MyTask: require('./task.vue')
},
methods: {
addTask: function() {
this.list.add(new Task)
}
}
}
リアクティブなデータバインディング機構により、 list
の内容が増えれば自動的に View も変化があるので、実装はかなり単純にかけるのがわかるかと思います。
イベントのバインディングについてのさらなる詳細は ガイドのメソッドとイベントハンドリング をご参照ください
(参考: ここまでの実装 https://github.com/ayasuda/hello_vuejs/commit/515763ea569f43802b0905038be554d79ebaa777)
フォームのバインディング
次は、ユーザーの入力によりモデルを変化させてみましょう。
Webページでのユーザー入力といえば <a>
, <input>
, <textare>
などが該当します。これらのタグとモデルとを結びつけるディレクティブが v-model
です。
もっともシンプルな例が次の通りで、このコードでは ViewModel の message というプロパティと <input>
タグの内容とを結びつけています。
<input type="text" v-model="message">
これを意識して、 components/task.vue の <template>
部分を次の様に書き換えてみてください。
<template>
<div>
<label>タイトル</label>
<input type="text" v-model="task.title" placeholder="タイトルを入力してください">
</div>
</template>
ブラウザで実行し、ユーザーの入力に応じてモデルの内容が書き換わっているのを試してみてください。
v-model
で紐づけることができるのは ViewModel の data で指定されたプロパティか props で親から渡ってきたプロパティです。
その他の <input>
や <textarea>
に ViewModel のプロパティをアサインする方法などについては、ガイドのフォーム入力バインディングを見ていただければと思います。
(参考: ここまでの実装 https://github.com/ayasuda/hello_vuejs/commit/5dec65876e7c5b5a965ecd3dcd88ece9a7001867)
子コンポーネントから親コンポーネントへのイベントディスパッチ
ここまででタスクリストの描画とデータの変更はできましたが、タスクリストを保存するためには TaskList.save()
メソッドの呼び出しが必要です。
ユーザーの入力が終わったら自動的に保存させるためには、 components/task.vue の中で TaskList.save()
を呼ぶべきでしょうか?
いいえ。それをしてしまうと、 compoents/task.vue の依存関係が増えてしまいます。
依存関係を薄く保つために、子コンポーネントから親コンポーネントへの通信手段としてイベントのディスパッチ機能を Vue.js は提供しています。
まず、 components/task.vue と components/task_list.vue をそれぞれ次の様に書き換えてみてください。components/task_list.vue は <script>
部分のみを記載します。
<template>
<div>
<label>タイトル</label>
<input type="text"
v-model="task.title"
placeholder="タイトルを入力してください"
v-on:keyup.enter="saveTask"
v-on:blur="saveTask"
>
</div>
</template>
<script>
var Task = require('../model/task').default
export default {
props: {
task: { type: Object, default: () => { return new Task } }
},
methods: {
saveTask: function() {
this.$dispatch('task-changed')
}
}
}
</script>
<script>
var TaskList = require('../model/task_list').default
var Task = require('../model/task').default
export default {
props: {
list: { type: Object, default: () => { return new TaskList } }
},
components: {
MyTask: require('./task.vue')
},
methods: {
addTask: function() {
this.list.add(new Task)
}
},
events: { // listen する子コンポーネントからのイベントを記します。
'task-changed': function () {
this.list.save()
}
}
}
</script>
まずは components/task.vue の変更点を見てみましょう。 v-on
が2つ追加されていると思います。それぞれ、Enterキーの入力とフォーカスが外れたタイミングで saveTask
メソッドを呼び出す様に変更されているのがわかるかと思います。
saveTask
の実装がミソで、$dispatch(event_name)
メソッドを用いて task-cahnged
というイベントを発火しています。
あとは、 components/task_list.vue の方で、イベントを受け取るだけです。
(参考: ここまでの作業 https://github.com/ayasuda/hello_vuejs/commit/c928def5397d5b10086eba1973e63a71f473b0a5)
Vue のイベント送信では一緒に値も送信可能です。
送信方法はとても簡単で、 $dispatch()
メソッドの第2引数に値を指定してあげるだけです。
タスクの削除を実装しながら試してみましょう。
まず、タスクコンポーネントに削除ボタンとボタンクリック時のメソッドを実装します。
<template>
<div>
<label>タイトル</label>
<input type="text"
v-model="task.title"
placeholder="タイトルを入力してください"
v-on:keyup.enter="saveTask"
v-on:blur="saveTask"
>
<span v-on:click="removeTask">削除</span>
</div>
</template>
<script>
var Task = require('../model/task').default
export default {
props: {
task: { type: Object, default: () => { return new Task } }
},
methods: {
saveTask: function() {
this.$dispatch('task-changed')
},
removeTask: function() {
// このコンポーネントが監視しているタスクをイベントと一緒に送信します。
this.$dispatch('task-removed', this.task)
},
}
}
</script>
次に、タスクリストコンポーネントにて、受け取ったタスクを元にタスクリストの更新処理を実装します。events
の部分を次のように書き換えてください。
{
events: {
'task-changed': function () {
this.list.save()
},
'task-removed': function (task) { // イベントと一緒に送信されたタスクを受け取ります。
// 受け取ったタスクを自身の管理するリストから削除するだけです!!
var index = this.list.tasks.indexOf(task)
this.list.tasks.splice(index, 1)
this.list.save()
}
}
}
実装が終わったらブラウザで動きを試してみましょう。子コンポーネントから親コンポーネントへのイベント伝播も、かなりシンプルにかけるのを実感していただけたかと思います。
(参考: ここまでの作業
https://github.com/ayasuda/hello_vuejs/commit/a31ead406ad00db6510edaa79447268ada7f905a)
スタイルの適用について
コンポーネントごとにスタイルを適用させることができれば、コンポーネントの再利用性がさらに上がるはずです。
Vueify を使うと、 <style>
というブロックを定義することができます!!
早速、コンポーネントを次のように編集しましょう。( <script>
タグ部分はそれぞれ省略 )
<style>
.my-task {
color: #f0c;
}
</style>
<template>
<div class="my-task">
<label>タイトル</label>
<input type="text"
v-model="task.title"
placeholder="タイトルを入力してください"
v-on:keyup.enter="saveTask"
v-on:blur="saveTask"
>
<span v-on:click="removeTask">削除</span>
</div>
</template>
<style>
.my-task-list {
color: #ffcc00;
}
</style>
<template>
<div class="my-task-list">
<div>{{list.title}}</div>
<ul>
<li v-for="task in list.tasks">
<my-task :task="task"></my-task>
</li>
</ul>
<div v-on:click="addTask">タスクを追加する</div>
</div>
</template>
ビルドし、ブラウザで挙動を確認するとうまく文字に色がつけられているのがわかるかと思います。
注意点として、 .vueファイルで定義したスタイルは文書全体に割り当てられます。 つまり、この中で、例えば body { display:none; }
と書くと、文書全体が非表示になってしまいます。
そこで、運用上のやりやすさを考慮し、 コンポーネント全体をコンポーネント名と同じ class を記した <div>
で囲む ことと、そのクラスに対してのスタイルを記す方法をオススメいたします。
(参考: ここまでの作業 https://github.com/ayasuda/hello_vuejs/commit/7e7e0019be61cabb41435907bcb1df0f068472b0)
最後に&作り込みの時間!
この記事をお読みいただき、手元で試していただくことで、Vue.js の強力さと柔軟さを体験していただけたかなと思います。非常にシンプルな記述で、モデル・ビジネスロジックから分離したコンポーネントシステムが定義できるのがお分かりいただけたかと思います。
説明していないこと
本記事では Vue.js の残りの機能として、次の要素については特に触れておりません。それぞれガイドへのリンクを貼っておきますので、ご一読いただければ幸いです。
-
フィルタ : バインディング構文にパイプ('|')シンボルをつなげて出力を調整する機能です。例:
{{ task.title | capitalize }}
- トランジション : 要素の削除や非表示化などをする際にアニメーションを簡単につけられる機能です。本文書では著者がかっこいいスタイルを作るあたりで力尽きたため説明が省かれています。
-
スロット : 親コンポーネントから子コンポーネントにアクセスする手段の一つとして、
<my-component>ここに何かコンテント置ける</my-component>
ようにする API です。
また、単体テスト手法や、動的なコンポーネントの切り替え、遅延したコンポーネント読み込みなどには触れていません。
作り込みと gulp タスクのアップデート
ここまでの作業でコンポーネントごとに見た目や動きを簡単に変更できるようになっているはずです。例えば、タスクコンポーネントの html だけを変更し、見た目を変えるのも簡単にできるようになっているはずです。
しかし、ファイルを変更するたびに毎度ビルドするのは手間です。また、差分ビルドにすることでビルドの高速化が実現できます。Watchifyでbrowserifyを差分ビルド を参考にすれば、より良い開発環境が作れるでしょう。
本記事ではコンポーネント中の html, css 共にテンプレートエンジンを用いませんでした。
Vueify の前後にその他のプリプロセッサを挟むことで、haml や Stylus を用いることができるようになります。
薄々感じていると思いますが、本記事でのタスクモデルはそこそこばっちいです。タスクリストへのタスクの追加と削除のインターフェースが揃っていないあたりに、コードの匂いを感じます。
どこまでの処理を ViewModel に書いて、どこからの処理を Model に書けばいいかは明確な答えがまだまだ出せない問題です。ぜひよりよく改善してみてください。
最後に
本記事をお読みいただきありがとうございました。フロントエンドフレームワークは日進月歩の勢いで進歩しており、Vue.js もそのうち淘汰されるのでしょう。(その意味で jQuery は偉大です)
ですが、 Vue.js は数あるフレームワークの中でも、かなりコンパクトにまとまっており、使いやすいフレームワークといえるでしょう。
(逆に言えばモデル部分やビジネスロジック部分の実装が使用者に任せられている難しさ、例えば、コンポーネント名とディレクトリツリーを一緒にするしない自由があります。)
本文書に対しての編集リクエストや改善案については歓迎いたします。より良い書き方について、ご指導・アドバイスいただけたら大変嬉しく感じます。
それでは、良いフロントエンド開発を。