利用 Vue 实现一个简单的待办列表
我们将使用 Vue.js 3.0 实现一个简单的待办列表( Todo List )应用。具体用到的知识点如下:
- Vue 组件基础。包含组件的全局注册、传递 props 和监听事件。
- Vue 列表渲染。
- Vue 事件处理。
- Vue 表单输入绑定。
- Vue 样式绑定。
- JS 数组方法。包含
map
和filter
。
通过 CDN 使用 Vue
由于我们只是实现一个简单的应用,我们采用 CDN 的方式引入 Vue。
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app"></div>
<script>
const { createApp } = Vue
// 创建应用实例
const app = createApp({})
// 挂载应用
app.mount('#app')
</script>
组件树
Todo List 应用的组件树如下:
TodoList (根组件)
├─ TodoForm (待办项表单组件)
└─ TodoItem-1 (具体待办项)
└─ TodoItem-2 (具体待办项)
└─ TodoItem-3 (具体待办项)
...
└─ TodoItem-n (具体待办项)
定义根组件
「根组件」是一个对象,是传入 createApp
参数。每个应用都需要一个「根组件」,其他组件将作为其子组件。在这里,我们的根组件是 TodoList
对象。
...
// 定义根组件
const TodoList = {}
// 创建应用实例
const app = createApp(TodoList)
...
待办项结构
我们的根组件需要包含每个待办项所需要的各种信息。一个待办项需要包含如下信息:
- 待办项的 ID。
- 待办项的具体内容。
- 该待办项是否已完成。
根组件数据
因此,我们需要在根组件中定义一个待办项的列表。我们可以先添加 3 个待办项。
// 定义根组件
const TodoList = {
data() {
return {
items: [
{
id: 1,
content: 'Do homework',
completed: false
},
{
id: 2,
content: 'Do chores',
completed: false
},
{
id: 3,
content: 'Cook dinner'
completed: false
}
]
}
}
}
根组件方法
一个组件除了要包含数据之外,还需要包含方法。因此需要定义添加和删除待办项的方法。
// 定义根组件
const TodoList = {
data() {
return {
items: [...]
}
},
methods: {
// 添加待办项
addTodo(value) {
this.items.push({
id: new Date().getTime(), // 以时间戳为ID
content: value,
completed: false
})
},
// 删除待办项
removeTodo(id) {
this.items = this.items.filter(item => item.id !== id)
}
}
}
与此同时,我们还需要增加一个修改待办项「完成状态」的方法。一个待办项可以被用户标记为「已完成」或者「未完成」。
// 定义根组件
const TodoList = {
data() {
return {
items: [...]
}
},
methods: {
// 添加待办项
addTodo(value) {
...
},
// 删除待办项
removeTodo(id) {
...
},
// 修改待办项的完成状态
changeCompleted(id) {
this.items = this.items.map(item => {
if (item.id === id) {
item.completed = !item.completed
}
return item
})
}
}
}
至此,我们的根组件已经定义完成。
定义表单组件
我们采用全局注册的方式来定义组件,因此需要使用「应用实例」。
...
// 定义根组件
const TodoList = {...}
// 创建应用实例
const app = createApp(TodoList)
// 定义表单组件
app.component('todo-form', {})
// 挂载应用
app.mount('#app')
表单的模板、数据和方法
表单是用来添加待办项的,所以一个表单组件包含一个文本输入框和一个提交按钮。
app.component('todo-form', {
template: `
<input type="text" placeholder="Add new todo..." />
<button>Complete</button>
`,
data() {
return {
inputValue: '' // 表单输入框的内容
}
}
})
我们可以使用 v-model
指令将表单输入框的内容同步给 todo-form
组件中的 inputValue
。
app.component('todo-form', {
template: `
<input type="text" placeholder="Add new todo..." v-model="inputValue" />
<button>Complete</button>
`,
data() {
return {
inputValue: '' // 表单输入框的内容
}
}
})
使用组件
现在我们可以将 todo-form
子组件放在根组件内。注意,在 DOM 模板中,我们必须显式地关闭标签。
<div id="app">
<todo-form></todo-form>
</div>
与此同时,我们也需要在点击按钮之后,触发添加待办项的事件。我们需要和父组件进行通信,将「添加待办项」这一事件通知到父组件 TodoList
。
app.component('todo-form', {
template: `
<input type="text" placeholder="Add new todo..." v-model="inputValue" />
<button @click="addTodo">Complete</button>
`,
data() {
return {
inputValue: '' // 表单输入框的内容
}
},
methods: {
addTodo() {
this.$emit('add-todo', this.inputValue)
}
}
})
在父组件中监听子组件中定义的事件,并绑定该事件。
<div id="app">
<todo-form @add-todo="addTodo"></todo-form>
</div>
定义待办项组件
用户在添加完待办项之后,需要在表单下面显示待办项列表,因此我们可以定义一个待办项组件。
...
// 创建应用实例
const app = createApp(TodoList)
// 定义表单组件
app.component('todo-form', {})
// 定义待办项组件
app.component('todo-item', {})
// 挂载应用
app.mount('#app')
待办项组件基本结构
待办项组件内部包含如下三部分,每一部分包含一个功能。
- 一个行内文本。用于显示当前待办项的名称。
- 一个选择框。用于改变当前待办项的完成状态。
- 一个按钮。用于删除当前待办项。
// 定义待办项组件
app.component('todo-item', {
template: `
<li>
<input type="checkbox" />
<span></span>
<button>Delete</button>
</li>
`
})
向组件中传数据
在父组件中引用 todo-item
组件。我们使用「列表渲染」来实现显示待办项组件列表,并传入待办项信息。
<div id="app">
<todo-form @add-todo="addTodo"></todo-form>
<ul>
<todo-item
v-for="item of itmes" :key="item.id"
:todo="item"
>
</todo-item>
</ul>
</div>
我们还需要在组件中接收父组件传过来的待办项具体信息作为 props。接收到 props 之后,我们才能实现待办项的上述三个功能。
// 定义待办项组件
app.component('todo-item', {
props: ['todo'],
template: `
<input type="checkbox" @click="changeCompleted(todo.id)" />
<span>{{ todo.content }}</span>
<button @click="removeTodo(todo.id)">Delete</button>
`,
methods: {
changeCompleted(id) {
this.$emit('change-completed', id)
},
removeTodo(id) {
this.$emit('remove-todo', id)
}
}
})
监听组件中的事件
在父组件中监听子组件的事件。
<div id="app">
<todo-form @add-todo="addTodo"></todo-form>
<ul>
<todo-item
v-for="item of itmes" :key="item.id"
:todo="item"
@change-completed="changeCompleted"
@remove-todo="removeTodo"
>
</todo-item>
</ul>
</div>
至此,我们的基本功能就完成了。
样式绑定
绑定内联样式。
// 定义待办项组件
app.component('todo-item', {
props: ['todo'],
template: `
<input type="checkbox" @click="changeCompleted(todo.id)" />
<span :style="{ textDecoration: todo.completed ? 'line-through' : 'none' }">
{{ todo.content }}
</span>
<button @click="removeTodo(todo.id)">Delete</button>
`,
methods: {
...
}
})
至此,我们的待办列表应用已经全部完成。