Posted on

Vue.js+TypeScriptで外部APIを使ったTODOリストを作ってみた

Vue.jsで外部APIを使ったTODOリストを作ってみた に続き、それのTypeScript版を作ってみました。TODOリスト用のAPIは以前書いたこちらのAPI「Go言語 GORM+GinでTODOリストのAPIを作ってみた」を利用します。CORSを全て許可しているのでどこからでも叩けるようになっています。TypeScriptを書くのは今回が初めてなので、誤っている箇所やもっとよい書き方などがあれば指摘して頂ければと思います。

できたもの

できたものはこちらです。
http://vuejs-ts.taisablog.com/todo

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.vueTodoList.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コンポーネントを呼び出します。


  
Vue logo
import { Component, Vue } from 'vue-property-decorator' import TodoList from '@/components/TodoList.vue' @Component({ components: { TodoList, }, }) export default class Todo extends Vue {}

components/TodoList : importとクラス定義

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はもう少し書き直したいです。


    

Vue.js TODO List

  • {{ todo.Text }}
    {{ todo.Text }}
    X

TodoList.vue全部


    

Vue.js TODO List

  • {{ todo.Text }}
    {{ todo.Text }}
    X
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 } }

ソース

https://github.com/taisa831/sandbox-vuejs-ts

まとめ

TypeScriptを書くのが初めてだったので(書籍自体あまりなかったですが)事前に2冊購入して読みました。学習コストが大変かと思っていましたが、評判通りJavaやC#のようなサーバサイドのように書けるので書きやすくてよいです。