- APIのエンドポイント
- プロジェクト作成
- views/Todo.vue
- components/TodoList
- インスタンス変数定義
- createdフック
- 一覧を取得する
- タスクを追加する
- タスクを削除する
- タスクを完了にする
- HTML
- TodoList.vue全部
- ソース
- まとめ
Vue.jsで外部APIを使ったTODOリストを作ってみた に続き、それのTypeScript版を作ってみました。TODOリスト用のAPIは以前書いたこちらのAPI「Go言語 GORM+GinでTODOリストのAPIを作ってみた」を利用します。CORSを全て許可しているのでどこからでも叩けるようになっています。TypeScriptを書くのは今回が初めてなので、誤っている箇所やもっとよい書き方などがあれば指摘して頂ければと思います。
APIのエンドポイント
APIのエンドポイントは以下としました。
URL http://gin.taisablog.com/api/v1/
GET /todo // 一覧表示
POST /todo // 新規作成
GET /todo/:id // 編集画面表示
PUT /todo/:id // 編集(今回未使用)
DELETE /todo/:id // 削除
プロジェクト作成
vue-cli
を使ってプロジェクト作成をしました。プロジェクト作成のコマンドを打つと、色々と聞かれますが、TypeScriptを利用する為にManually select features
を選択し、TypeScript
をONにします。ここではRouter
もONにしました。
% npm install -g @vue/cli
% vue create my-project
default (babel, eslint)
❯ Manually select features
vue-cli
でできたプロジェクトのsrc
配下の構成は以下となっています。今回はそこにTodo.vue
とTodoList.vue
を追加して実装しました。views
配下で実装するだけでも大丈夫ですが、今回はあえてviews/Todo.vue
からTodoList.vue
コンポーネントを呼び出す形としました。
.
├── App.vue
├── assets
│ └── logo.png
├── components
│ ├── HelloWorld.vue
│ └── TodoList.vue ← 新規追加
├── main.ts
├── router.ts
├── shims-tsx.d.ts
├── shims-vue.d.ts
└── views
├── About.vue
├── Home.vue
└── Todo.vue ← 新規追加
views/Todo.vue
TodoList.vueコンポーネントを呼び出します。
<div><figure><img src="../assets/logo.png" alt="Vue logo"></figure></div>
import { Component, Vue } from 'vue-property-decorator'
import TodoList from '@/components/TodoList.vue'
@Component({
components: {
TodoList,
},
})
export default class Todo extends Vue {}
components/TodoList
axios
を利用するのでインストールします。
% npm i axios
import { Component, Vue } from 'vue-property-decorator'
import axios from 'axios'
const NOT_STARTED = 1
const FINISHED = 3
@Component
export default class TodoList extends Vue {
// ここに実装
}
インスタンス変数定義
private todoList: string[] = []
private inputField: string = ''
private baseUrl: string = 'http://gin.taisablog.com/api/v1/'
createdフック
createdフックでロード時に一覧を取得します。
public created() {
this.getTodo()
}
一覧を取得する
public async getTodo() {
try {
const response = await axios.get(this.baseUrl + 'todo')
this.todoList = response.data
return this.todoList
} catch (e) {
return e
}
}
タスクを追加する
public async addTodo() {
if (!this.inputField) {
return
}
try {
const params = {
text: this.inputField,
status: 1,
}
await axios.post(this.baseUrl + 'todo',JSON.stringify(params))
this.getTodo()
this.inputField = ''
} catch (e) {
return e
}
}
タスクを削除する
public async deleteTodo(todo: any) {
try {
await axios.delete(this.baseUrl + 'todo/' + todo.ID)
this.getTodo()
} catch (e) {
return e
}
}
タスクを完了にする
public async toggle(todo: any) {
try {
let status = 0
if (todo.Status === NOT_STARTED) {
status = FINISHED
} else {
status = NOT_STARTED
}
const params = {
'{status}': status,
}
await axios.put(this.baseUrl + 'todo/' + todo.ID,JSON.stringify(params))
todo.Status = status
} catch (e) {
return e
}
}
HTML
HTMLとCSSはもう少し書き直したいです。
<div class="todoList">
<h1>Vue.js TODO List</h1>
<section>
<div class="inputWrapper clearfix">
<div class="txtBoxWrapper"> </div>
<div class="addBtnWrapper"><button class="addBtn">Add</button></div>
</div>
</section>
<section>
<div>
<ul>
<li>
<div class="todo"><label class="chkboxLabel"> </label>
<h5 class="todoTxt Finished">{{ todo.Text }}</h5>
<h5 class="todoTxt NotStarted">{{ todo.Text }}</h5>
<span class="deleteBtn">X</span></div>
</li>
</ul>
</div>
</section>
</div>
TodoList.vue
全部
<div class="todoList">
<h1>Vue.js TODO List</h1>
<section>
<div class="inputWrapper clearfix">
<div class="txtBoxWrapper"> </div>
<div class="addBtnWrapper"><button class="addBtn">Add</button></div>
</div>
</section>
<section>
<div>
<ul>
<li>
<div class="todo">
<label class="chkboxLabel"> </label>
<h5 class="todoTxt Finished">{{ todo.Text }}</h5>
<h5 class="todoTxt NotStarted">{{ todo.Text }}</h5>
<span class="deleteBtn">X</span>
</div>
</li>
</ul>
</div>
</section>
</div>
import { Component, Vue } from 'vue-property-decorator'
import axios from 'axios'
const NOT_STARTED = 1
const FINISHED = 3
@Component
export default class TodoList extends Vue {
private todoList: string[] = []
private inputField: string = ''
private baseUrl: string = 'http://gin.taisablog.com/api/v1/'
public created() {
this.getTodo()
}
public async getTodo() {
try {
const response = await axios.get(this.baseUrl + 'todo')
this.todoList = response.data
return this.todoList
} catch (e) {
return e
}
}
public async addTodo() {
if (!this.inputField) {
return
}
try {
const params = {
text: this.inputField,
status: 1,
}
await axios.post(this.baseUrl + 'todo',JSON.stringify(params))
this.getTodo()
this.inputField = ''
} catch (e) {
return e
}
}
public async deleteTodo(todo: any) {
try {
await axios.delete(this.baseUrl + 'todo/' + todo.ID)
this.getTodo()
} catch (e) {
return e
}
}
public async toggle(todo: any) {
try {
let status = 0
if (todo.Status === NOT_STARTED) {
status = FINISHED
} else {
status = NOT_STARTED
}
const params = {
'{status}': status,
}
await axios.put(this.baseUrl + 'todo/' + todo.ID,JSON.stringify(params))
todo.Status = status
} catch (e) {
return e
}
}
}
.todoList {
width: 100%;
}
.clearfix::after {
content: '';
display: block;
clear: both;
}
.inputWrapper {
position: relative;
width: 380px;
margin: auto;
display: block;
}
.inputWrapper input[type='text'] {
font: 15px/24px sans-serif;
box-sizing: border-box;
width: 100%;
padding: 0.3em;
transition: 0.3s;
letter-spacing: 1px;
border: 1px solid #1b2538;
border-radius: 4px;
}
.ef input[type='text']:focus {
border: 1px solid #da3c41;
outline: none;
box-shadow: 0 0 5px 1px rgba(218, 60, 65, .5);
}
.txtBoxWrapper {
float: left;
width: 270px;
}
.addBtnWrapper {
float: right;
}
.addBtn {
position: relative;
display: block;
text-decoration: none;
color: #FFF;
background: #007bff;
border: solid 1px #007bff;
border-radius: 4px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
width: 100px;
height: 35px;
font-size: 16px;
}
ul {
list-style: none;
}
li {
border: 1px solid #dee2e6;
border-top-left-radius: .25rem;
border-top-right-radius: .25rem;
margin: 10px auto;
width: 50%;
height: 80px;
}
.todo {
display:flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
}
.chkboxLabel {
width: 20px;
display: inline-block;
text-align: left;
margin-right: 10px;
}
.chkbox {
transform: scale(1.3);
margin-left: 10px;
}
.todoTxt {
font-size: 20px;
width: 100%;
text-align: center;
vertical-align: middle;
display: inline-block;
}
.todoTxt.NotStarted {
text-decoration: none;
}
.todoTxt.Finished {
text-decoration: line-through;
}
.deleteBtn {
color: pink;
text-align: right;
margin-right: 20px;
margin-left: 10px;
width: 20px;
display: block;
font-size: 20px;
cursor: pointer;
}
@media screen and (max-width: 520px) {
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
border: 1px solid #dee2e6;
border-top-left-radius: .25rem;
border-top-right-radius: .25rem;
margin: 10px 0;
width: 100%;
height: 80px;
padding: 0;
}
.inputWrapper {
margin: 0px auto
}
}
ソース
まとめ
TypeScriptを書くのが初めてだったので(書籍自体あまりなかったですが)事前に2冊購入して読みました。学習コストが大変かと思っていましたが、評判通りJavaやC#のようなサーバサイドのように書けるので書きやすくてよいです。