はじめに
todoアプリ開発の続きです。
前回は環境構築してVITEでローカルサーバを起動するところまで進めました。
今回はいよいよ実装を進めます。
参考書籍
『Vue.js 超入門 v3.4+: 最初に手にしてよかった本』
実際にアプリを作っていく
実現する機能
- TODOを追加できるテキスト入力欄
- 残TODOが一覧で見える一覧表示欄
- 内容編集する機能
- 完了チェックで打消線がつく機能
- TODOを削除する機能
実装内容
ローカルサーバを起動してindex.htmlを呼び出すところから実装を見ていきます。
index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
ここでは、main.ts
を呼び出しているだけです。
次はmain.ts
を見てみます。
main.ts
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
このコードでは、createApp関数を使ってVueアプリケーションのインスタンスを作成し、App.vueというルートコンポーネントを指定して、HTMLの#app要素にマウントします。
ルートコンポーネントは他のすべてのコンポーネントを包含する「土台」として考えることができます。これにより、アプリケーションの各部分が独立して機能しつつ、全体として統合された動作を実現します。Vue3では、ルートコンポーネントを通じて、状態管理やルーティングなどの機能を一元的に管理することが可能です。1
App.vueは単一ファイルコンポーネントと呼ばれるscript, HTML, CSS を1つのファイルに書く構造となっています。
<script>
タグで制御を記載する。
<template>
タグで部品配置などHTMLに相当する部分を記載する。
<style>
タグでデザインを記載する。
今回のアプリでは具体的に以下のように記載しました。
App.vue
<script setup lang="ts">
import MainTodo from '@/components/MainTodo.vue' // メイン部の呼び出し
import TheFooter from '@/components/TheFooter.vue' // ヘッダー部の呼び出し
import TheHeader from '@/components/TheHeader.vue' // フッター部の呼び出し
</script>
<template>
<div class="warp">
<TheHeader /> <!-- ヘッダー部 -->
<main class="main"><MainTodo /></main> <!-- メイン部 -->
<TheFooter /> <!-- フッター部 -->
</div>
</template>
<sytle>
はTheHeader、MainTodo、TheFooterでそれぞれ記載しているので、ここでは書いていません。
ヘッダー部、フッター部はそれぞれ次のように記載しています。
TheHeader.vue
<template>
<h1 class="title">TODO</h1>
</template>
<style scoped>
.title {
width: 100%;
padding: 16px 0;
margin: 0;
text-align: center;
background-color: #fff;
}
</style>
TheFooter.vue
<template>
<footer class="footer">© yorocovich</footer>
</template>
<style scoped>
.footer {
width: 100%;
height: 30px;
text-align: center;
box-shadow: 0 1px 1px #ddd;
}
</style>
<style scoped>
のようにscopedを記載することで、スタイルの適用範囲を同vueファイル内に限定することができます。
メイン部のMainTodoは次のようになっています。
MainTodo.vue
<script setup lang="ts">
import { ref } from 'vue'
import { useTodoList } from '@/composables/useTodoList'
const { todoList, add, edit, check } = useTodoList()
const todo = ref<string | undefined>()
const addTodo = () => {
if (!todo.value) return
add(todo.value)
todo.value = ''
}
const editTodo = (_id: number) => {
edit(_id)
}
const changeCheck = (_id: number) => {
check(_id)
}
</script>
<template>
<div>
<input
type="text"
class="todo_input"
v-model="todo"
@keyup.enter="addTodo"
placeholder="TODOを入力"
/>
</div>
<div class="box_list">
<div class="todo_list" v-for="todo in todoList" :key="todo.id">
<div class="todo" :class="{ fin: todo.checked }">
<input
type="checkbox"
class="check"
@change="changeCheck(todo.id)"
:checked="todo.checked"
/>
<input
v-show="!todo.checked"
type="text"
class="task"
@keyup.enter="editTodo(todo.id)"
@blur="editTodo(todo.id)"
v-model="todo.task"
/>
<label v-show="todo.checked">{{ todo.task }}</label>
</div>
</div>
</div>
</template>
<style scoped>
.todo_input {
width: 250px;
padding: 6px 8px;
margin-right: 8px;
font-size: 18px;
border: 1px solid #aaa;
border-radius: 6px;
}
.box_list {
display: flex;
flex-direction: column;
gap: 2px;
margin-top: 4px;
}
.todo_list {
display: flex;
gap: 5px;
align-items: center;
}
.todo {
width: 300px;
padding: 4px 1px;
}
.check {
margin-right: 12px;
transform: scale(1.6);
}
.task {
width: 120px;
font-size: 15px;
border: 0px;
}
.fin {
font-size: 15px;
color: #777;
text-decoration: line-through;
background-color: #ddd;
}
</style>
以下軽く解説
import { ref } from 'vue'
ref は Vue3 の Composition API で導入された関数で、リアクティブな値を作成するために使用されます。更新を画面をリアルタイムで反映してくれるスグレモノです。
<input type="text" class="todo_input" v-model="todo" @keyup.enter="addTodo" placeholder="TODOを入力" />
新規TODOを入力するテキストボックスです。
EnterキーでaddTodo
を呼び出してLocalStorageにTODOテキストを保存しています。
<div class="todo_list" v-for="todo in todoList" :key="todo.id">
TODOリストを作成しているところです。
LocalStorageにあるtodoListを1件ずつ配列で取得しています。
v-for
をつかうことでループ処理を実現しています。
<input type="checkbox" class="check" @change="changeCheck(todo.id)" :checked="todo.checked" />
チェックボックスです。
チェックするとchangeCheck
を呼び出してチェック処理します。
<input v-show="!todo.checked" type="text" class="task" @keyup.enter="editTodo(todo.id)" @blur="editTodo(todo.id)" v-model="todo.task" />
TODOリストのテキスト部です。
テキストを変更するとeditTodo
を呼び出して編集しています。フォーカスアウトのときも同じ動作します。
最後に、これらの挙動を制御するスクリプト部です。
useTodoList.ts
import { ref } from 'vue'
export const useTodoList = () => {
const todoList = ref<{ id: number; task: string; checked: boolean }[]>([])
const ls = localStorage.todoList
// ローカルストレージにtodoListが存在していればparseし、
// なければ空配列をセット
todoList.value = ls ? JSON.parse(ls) : []
const findById = (_id: number) => {
return todoList.value.find((todo) => todo.id === _id)
}
const findIndexById = (_id: number) => {
return todoList.value.findIndex((todo) => todo.id === _id)
}
const add = (_task: string) => {
const id = new Date().getTime()
todoList.value.push({ id: id, task: _task, checked: false })
localStorage.todoList = JSON.stringify(todoList.value)
}
const edit = (_id: number) => {
const todo = findById(_id)
const idx = findIndexById(_id)
if (!todo) return
if (todo?.task === '') {
del(_id)
} else {
todoList.value.splice(idx, 1, todo) // splice関数で_idxを元にTODOを置換
localStorage.todoList = JSON.stringify(todoList.value)
}
}
const del = (_id: number) => {
const todo = findById(_id)
if (todo) {
const idx = findIndexById(_id)
todoList.value.splice(idx, 1)
localStorage.todoList = JSON.stringify(todoList.value)
}
}
const check = (_id: number) => {
const todo = findById(_id)
const idx = findIndexById(_id)
if (todo) {
todo.checked = !todo.checked //反転
todoList.value.splice(idx, 1, todo)
localStorage.todoList = JSON.stringify(todoList.value)
}
}
return { todoList, add, edit, check }
}
return { todoList, add, edit, check }
で作成した関数を戻しています。
MainTodo.vueのスクリプト部でconst { todoList, add, edit, check } = useTodoList()
としてキャッチしています。
さいごに
初めてVueに触れましたが、すごく進化していて楽しかったです。
業務でWeb開発(JSP+Java)していたのが2010年頃なので、まったく違う体験をしました。「今ってこうなってるのーー!」という衝撃を受けました。
どこまで書いていいのか、どう書いていいのか、初めてのことばかりで文章が雑だと感じています。
内容の推敲は今後時間があるときに行っていきたいと思います。
書籍「Vue.js 超入門3.4+」を片手(iPad)に開発をしてきましたが、
本書の手順に忠実に従うことで、スムーズにアプリを完成させることができました。
説明が適切なタイミングで、適切な分量で出てくるので、読み進めやすかったです。
Vue初学者の方はぜひともご一読してみてはいかがでしょうか。