Vue.js 入門:從零開始作一個極簡 To-Do 應用

寫做時間:2019-12-10javascript

版本信息:Vue.js 2.6.10css

官網文檔:cn.vuejs.org/html

前言

學習 Vue 的最佳方式之一是「請馬上查閱 Vue.js 的官方文檔」,簡單看一下「基礎」部分,配合本文食用更佳。vue

image.png

在開始寫代碼以前,首先去 BootCDN 上找一下目前最新版本完整版的 Vue.js 的連接:cdn.bootcss.com/vue/2.6.10/… ,與壓縮版(vue.min.js)不一樣,它(vue.js)包含完整的警告和調試模式。java

爲了儘量地保持簡單,本文不使用 Vue CLI 來構建項目,而是像用 jQuery 開發那樣,直接在 HTML 文件裏引入 Vue.js 文件。app

若是文章中的代碼有不明白的地方,個人建議是:直接抄代碼,看效果,看文檔,改代碼,看效果,如此循環往復。ide

聲明式渲染

瞭解一下 Vue 官網的基礎部分的「聲明式渲染」部分 ,咱們能夠建立以下代碼:函數

image.png

此時預覽 index.html 文件,會看到頁面上出現 Hello, Vue.js! 的文字。學習

組件結構

咱們把要作的 To-Do App 拆分紅一個個小組件,目前先來一個組件 TodoList 和一個它的子組件 TodoItem 。經過熟悉 官網上的「組件基礎」教程 咱們來繼續作下去。ui

TodoList 組件

先來作一下 TodoList 組件。我把代碼都粘貼過來,方便取用學習。 此文章以後的代碼都是在下面這段代碼的基礎上修改,到時我就只放修改的部分代碼了,有必要的時候我會把所有代碼貼出來。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Vue.js To-Do App</title>
</head>
<body>
  <div id="app">
    <todo-list></todo-list>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
  <script> Vue.component('todo-list', { data: function() { return {} }, template:` <ul> <li>Todo A</li> <li>Todo B</li> <li>Todo C</li> </ul> ` }) new Vue({ el: '#app', data: {} }) </script>
</body>
</html>
複製代碼

須要注意的內容:

  1. 組件的代碼須要放到 new Vue 以前
  2. 定義組件的 data 時,data 必須是一個函數
  3. template 中的代碼必需要有個外層容器包裹(最外層只能有一個元素)

改寫 TodoList 組件,添加所需數據:

<!DOCTYPE html>
<html lang="zh-hans">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Vue.js To-Do App</title>
</head>
<body>
  <div id="app">
    <todo-list v-bind:todos="todos"></todo-list>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
  <script> Vue.component('todo-list', { data: function() { return {} }, props: ['todos'], template:` <div class="todo-list"> <p>已完成:{{todos.filter(todo => todo.done === true).length}}</p> <p>未完成:{{todos.filter(todo => todo.done === false).length}}</p> <div class="todo-item" v-for="todo in todos"> <div class="title">{{todo.title}}</div> <div class="content">{{todo.content}}</div> <div class="button" v-show="!todo.done">點擊完成</div> <div class="button" v-show="todo.done">已完成</div> </div> </div> ` }) new Vue({ el: '#app', data: { todos: [ { title: '待辦 1', content: '上課以前要抄一下同窗的做業。', done: false }, { title: '待辦 2', content: '課間和朋友去球場打籃球。', done: true }, { title: '待辦 3', content: '英語課上故意調皮一下讓美麗的英語老師注意到而後提問我。', done: false }, { title: '待辦 4', content: '放學後趕忙跑,絕對不能聽某些人的話:「放學後你給我等着」。', done: false } ] } }) </script>
</body>
</html>
複製代碼

須要注意的內容:

  1. 數據在 new Vue 的 data 中定義,須要傳遞給  TodoList 組件,使用 v-bind 指令,該代碼表示將 todos 變量傳遞給 TodoList 組件的 todos 屬性。
<div id="app">
  <todo-list v-bind:todos="todos"></todo-list>
</div>
複製代碼
  1. TodoList 組件定義 props 用來接收傳遞過來的 todos ,在組件的 template 中能夠直接使用。這裏咱們使用 v-for 來循環渲染數據。
Vue.component('todo-list', {
  // ... 省略 ...
  props: ['todos'],
	template:` <div class="todo-list"> <p>已完成:{{todos.filter(todo => todo.done === true).length}}</p> <p>未完成:{{todos.filter(todo => todo.done === false).length}}</p> <div class="todo-item" v-for="todo in todos"> <div class="title">{{todo.title}}</div> <div class="content">{{todo.content}}</div> <div class="button" v-show="!todo.done">點擊完成</div> <div class="button" v-show="todo.done">已完成</div> </div> </div> `
})
複製代碼

雖然寫了給某些元素寫了 class 可是尚未寫任何樣式,如今打開 index.html 預覽是這樣的:

image.png

TodoItem 組件

如今咱們把 class 爲 todo-item 的元素提取出來做爲單獨的組件,除此以外咱們什麼也不作,預覽效果與剛纔一致。

Vue.component('todo-item', {
  props: ['todo'],
  template: ` <div class="todo-item"> <div class="title">{{todo.title}}</div> <div class="content">{{todo.content}}</div> <div class="button" v-show="!todo.done">點擊完成</div> <div class="button" v-show="todo.done">已完成</div> </div> `
})

Vue.component('todo-list', {
  data: function() {
    return {}
  },
  props: ['todos'],
  template:` <div class="todo-list"> <p>已完成:{{todos.filter(todo => todo.done === true).length}}</p> <p>未完成:{{todos.filter(todo => todo.done === false).length}}</p> <todo-item v-for="(todo, index) in todos" v-bind:key="index" v-bind:todo="todo"> </todo-item> </div> `
})
複製代碼

接下來加入編輯的功能

Vue.component('todo-item', {
  props: ['todo'],
  data: function() {
    return {
      isEditing: false
    }
  },
  template: ` <div> <div class="todo-item" v-show="!isEditing"> <div class="title">{{todo.title}}</div> <div class="content">{{todo.content}}</div> <div class="button edit" v-on:click="showForm">編輯 ✏</div> <div class="button" v-show="!todo.done">點擊完成</div> <div class="button" v-show="todo.done">已完成</div> </div> <div class="todo-item" v-show="isEditing"> <div class="form"> <div class="field"> <label>Title</label> <input type="text" v-model="todo.title" /> </div> <div class="field"> <label>Content</label> <input type="text" v-model="todo.content" /> </div> <button class="close" v-on:click="closeForm">保存並關閉編輯模式</button> </div> </div> </div> `,
  methods: {
    showForm: function() {
      this.isEditing = true
    },
    closeForm: function() {
      this.isEditing = false
    }
  }
})

複製代碼

添加的代碼作了這幾點:

  1. 在 TodoItem 組件中添加編輯按鈕,並添加一個 isEditing 的屬性用來區分是不是編輯狀態。
  2. 添加編輯模式時 TodoItem 組件的代碼
  3. 添加並綁定打開和關閉編輯模式的事件

刪除 Todo

Vue.component('todo-item', {
  // ... 省略 ...
  template: ` <div> <div class="todo-item" v-show="!isEditing"> <div class="title">{{todo.title}}</div> <div class="content">{{todo.content}}</div> <div class="button edit" v-on:click="showForm">編輯 ✏</div> <div class="button delete" v-on:click="deleteTodo(todo)">刪除 ×××</div> <div class="button" v-show="!todo.done">點擊完成</div> <div class="button" v-show="todo.done">已完成</div> </div> // ... 省略 ... </div> `,
  methods: {
    // ... 省略 ...
    deleteTodo(todo) {
      this.$emit('delete-todo', todo)
    }
  },
})
複製代碼

在 TodoItem 組件中添加刪除按鈕,並添加刪除的方法,這個方法會向父組件 TodoList 發送一個 delete-todo 事件以及要刪除的 todo 數據。

父組件 TodoList 中添加一個刪除事件,並監聽來自子組件的 delete-todo 事件。

Vue.component('todo-list', {
  data: function() {
    return {}
  },
  props: ['todos'],
  template:` <div class="todo-list"> <p>已完成:{{todos.filter(todo => todo.done === true).length}}</p> <p>未完成:{{todos.filter(todo => todo.done === false).length}}</p> <todo-item v-for="(todo, index) in todos" v-bind:key="index" v-bind:todo="todo" v-on:delete-todo="deleteTodo" > </todo-item> </div> `,
  methods: {
    deleteTodo(todo) {
      const index = this.todos.indexOf(todo)
      this.todos.splice(index, 1)
    }
  },
})
複製代碼

新增 Todo

新建一個 AddTodo 組件,將組件添加到 TodoList 組件中。

Vue.component('add-todo', {
  data: function() {
    return {
      isAdding: false,
      todo: {
        title: '',
        content: '',
        done: false
      }
    }
  },
  template: ` <div> <div v-on:click="showForm">添加 +++</div> <div class="form" v-show="isAdding"> <div class="field"> <label>標題</label> <input type="text" v-model="todo.title" /> </div> <div class="field"> <label>內容</label> <input type="text" v-model="todo.content" /> </div> <button class="close" v-on:click="saveForm">保存</button> <button class="close" v-on:click="closeForm">取消</button> </div> </div> `,
  methods: {
    showForm() {
      this.isAdding = true
    },
    saveForm() {
      if (this.todo.title && this.todo.content) {
        this.$emit('add-todo', this.todo)
        this.closeForm()
      }
    },
    closeForm() {
      this.isAdding = false
      this.todo = {
        title: '',
        content: '',
        done: false
      }
    }
  }
})

Vue.component('todo-list', {
  // ... 省略 ...
  template:` <div class="todo-list"> <add-todo v-on:add-todo="addTodo"></add-todo> <p>已完成:{{todos.filter(todo => todo.done === true).length}}</p> <p>未完成:{{todos.filter(todo => todo.done === false).length}}</p> <todo-item v-for="(todo, index) in todos" v-bind:key="index" v-bind:todo="todo" v-on:delete-todo="deleteTodo" > </todo-item> </div> `,
  methods: {
    // ... 省略 ...
    addTodo(todo) {
      this.todos.push(todo)
    }
  },
})
複製代碼

AddTodo 組件默認只顯示一個添加按鈕,當點擊添加按鈕的時候顯示須要填寫的表單,填寫完成後點擊保存,將向父組件 TodoList 發送一個 add-todo 事件以及表單信息。

父組件 TodoList 監聽 add-todo 事件並在事件觸發後向 todos 數據中新增一條由 AddTodo 組件發送的數據。

完成 Todo

TodoItem 組件中,點擊完成按鈕,發送 complete-todo 事件給父組件 TodoList 。

父組件 TodoList 監聽 complete-todo 事件並在事件觸發後從 todos 數據中要完成的那條數據標記爲已完成。

Vue.component('todo-item', {
 // ... 省略 ...
  template: ` <div> <div class="todo-item" v-show="!isEditing"> <div class="title">{{todo.title}}</div> <div class="content">{{todo.content}}</div> <div class="button edit" v-on:click="showForm">編輯 ✏</div> <div class="button delete" v-on:click="deleteTodo(todo)">刪除 ×××</div> <div class="button" v-show="!todo.done" v-on:click="completeTodo(todo)">點擊完成</div> <div class="button" v-show="todo.done">已完成</div> </div> ...... `,
  methods: {
    // ... 省略 ...
    completeTodo(todo) {
      this.$emit('complete-todo', todo)
    }
  }
})

Vue.component('todo-list', {
  // ... 省略 ...
  template:` <div class="todo-list"> ...... <todo-item v-for="(todo, index) in todos" v-bind:key="index" v-bind:todo="todo" v-on:delete-todo="deleteTodo" v-on:complete-todo="completeTodo" > </todo-item> </div> `,
  methods: {
    // ... 省略 ...
    completeTodo(todo) {
      const index = this.todos.indexOf(todo)
      this.todos[index].done = true
    }
  }
})

複製代碼

至此,一個基本功能還算健全的 To-Do App 就完成了。

完整代碼

最終的完整代碼以下,能夠直接拿走本身運行預覽一下。

<!DOCTYPE html>
<html lang="zh-hans">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Vue.js To-Do App</title>
</head>
<body>
  <div id="app">
    <todo-list v-bind:todos="todos"></todo-list>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
  <script> Vue.component('add-todo', { data: function() { return { isAdding: false, todo: { title: '', content: '', done: false } } }, template: ` <div> <div v-on:click="showForm">添加 +++</div> <div class="form" v-show="isAdding"> <div class="field"> <label>標題</label> <input type="text" v-model="todo.title" /> </div> <div class="field"> <label>內容</label> <input type="text" v-model="todo.content" /> </div> <button class="close" v-on:click="saveForm">保存</button> <button class="close" v-on:click="closeForm">取消</button> </div> </div> `, methods: { showForm() { this.isAdding = true }, saveForm() { if (this.todo.title && this.todo.content) { this.$emit('add-todo', this.todo) this.closeForm() } }, closeForm() { this.isAdding = false this.todo = { title: '', content: '', done: false } } }, }) Vue.component('todo-item', { props: ['todo'], data: function() { return { isEditing: false } }, template: ` <div> <div class="todo-item" v-show="!isEditing"> <div class="title">{{todo.title}}</div> <div class="content">{{todo.content}}</div> <div class="button edit" v-on:click="showForm">編輯 ✏</div> <div class="button delete" v-on:click="deleteTodo(todo)">刪除 ×××</div> <div class="button" v-show="!todo.done" v-on:click="completeTodo(todo)">點擊完成</div> <div class="button" v-show="todo.done">已完成</div> </div> <div class="todo-item" v-show="isEditing"> <div class="form"> <div class="field"> <label>Title</label> <input type="text" v-model="todo.title" /> </div> <div class="field"> <label>Content</label> <input type="text" v-model="todo.content" /> </div> <button class="close" v-on:click="closeForm">保存並關閉編輯模式</button> </div> </div> </div> `, methods: { showForm: function() { this.isEditing = true }, closeForm: function() { this.isEditing = false }, deleteTodo(todo) { this.$emit('delete-todo', todo) }, completeTodo(todo) { this.$emit('complete-todo', todo) } }, }) Vue.component('todo-list', { data: function() { return {} }, props: ['todos'], template:` <div class="todo-list"> <add-todo v-on:add-todo="addTodo"></add-todo> <p>已完成:{{todos.filter(todo => todo.done === true).length}}</p> <p>未完成:{{todos.filter(todo => todo.done === false).length}}</p> <todo-item v-for="(todo, index) in todos" v-bind:key="index" v-bind:todo="todo" v-on:delete-todo="deleteTodo" v-on:complete-todo="completeTodo" > </todo-item> </div> `, methods: { deleteTodo(todo) { const index = this.todos.indexOf(todo) this.todos.splice(index, 1) }, addTodo(todo) { this.todos.push(todo) }, completeTodo(todo) { const index = this.todos.indexOf(todo) this.todos[index].done = true } }, }) new Vue({ el: '#app', data: { todos: [ { title: '待辦 1', content: '上課以前要抄一下同窗的做業。', done: false }, { title: '待辦 2', content: '課間和朋友去球場打籃球。', done: true }, { title: '待辦 3', content: '英語課上故意調皮一下讓美麗的英語老師注意到而後提問我。', done: false }, { title: '待辦 4', content: '放學後趕忙跑,絕對不能聽某些人的話:「放學後你給我等着」。', done: false } ] } }) </script>
</body>
</html>
複製代碼

(完)

相關文章
相關標籤/搜索