功能介绍
- 可以输入待办事项
- 输入回车键,添加一条待办事项,展现在输入框下,形成待办事项列表
- 某条待办事项不想要了,可以删除
- 某项待办事项完成了,可以勾选掉
- 还需要显示未完成事项
- 还可以全选
- 可以对待办事项筛选(已完成,未完成),切换待办事项的状态
- 能够本地存储
ToDoList 功能清单
- 添加待办事项
- 删除待办事项
- 编辑待办事项
- 切换待办事项
- 存储待办事项
项目结构
使用vue-cli 脚手架,完成vue3的项目搭建。
- main.js 项目入口,根实例的挂载
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
- App.vue 引入todoList样式,并且完成基本HTMl结构的搭建,为了消除vue3的学习噪音,样式和html架子代码可以自取
- html 结构
- header
- 标题(todos)
- 待办事项输入框
- section
- 待办事项列表
- footer
- 待办事项条目数
- 待办事项筛选项(全部,待办, 完成)
- header
接下来用我们前端学过的vue3的api完成todolist功能清单
功能1: 添加待办事项
分析: 添加待办事项文本框,需要绑定一个数据,当用户输入时记录下待办事项,回车后,将该条数据存入数组中,方便后面展示
<input
class="new-todo"
placeholder="What needs to be done?"
autocomplete="off"
autofocus
v-model="input" // 用变量input使用v-model绑定用户输入数据
@keyup.enter="addTodo" // 回车时调用添加todo方法addTodo
>
我们可以在setup里面完成todo添加功能,但是学过compositionAPI优点,就是让同一交互功能使用函数包装起来,等需要的时候再用,基于这个思想,我们把添加todo功能代码封装在一个函数里面
// 1. 添加待办事项
const useAdd = todos => {
// 使用ref 声明一个响应式基本数据,用于输入框的绑定,最后return出去
const input = ref('')
// 声明一个添加todo方法
const addTodo = () => {
const text = input.value && input.value.trim()
if(text.length === 0) return;
// 将todo存储到数组中,包括内容,和完成状态(添加时,未完成)
// 注意: todos也是响应式数据,所以是todos.value
todos.value.unshift({
text,
completed: false
})
}
return {
input,
addTodo
}
}
再在setup里面用上useAdd,声明一个响应式变量(todos),值为数组,收集添加的todo
...
setup () {
// 用于todo的存储,这个数据变化了todolist列表也要刷新所以也是响应式数据,使用ref
const todos = ref([])
return {
todos,
...useAdd(todos)
}
},
...
将收集到todos的todo渲染出来
<ul class="todo-list">
<li
v-for="todo in todos" // 使用v-for指令遍历todos
:key="todo"
:class="{completed: todo.completed }" // 用todo完成状态控制类
>
<div class="view">
<label>{{ todo.text }}</label> // 展示todo内容
</div>
</li>
</ul>
好!!! 先完成添加待办事项
删除todo
找到todo列表,在todolist里面添加一个删除按钮,绑定一个click事件,调用删除todo方法 remove
<ul class="todo-list">
<li
v-for="todo in todos"
:key="todo"
:class="{completed: todo.completed }"
>
<div class="view">
...
<label >{{ todo.text }}</label>
<button class="destroy" @click="remove(todo)"></button> // 删除按钮
</div>
...
</li>
</ul>
和添加一样,把删除功能进行封装成一个函数
const useRemove = todos => {
const remove = todo => {
const index = todos.value.indexOf(todo)
todos.value.splice(index, 1)
}
return {
remove // 返回remove方法给实例使用
}
}
注意 todos 是响应式数据,需要.value食用。有点难受
编辑待办事项
分析: 这个功能稍微负责一些,如果我们觉得已经添加的待办事项输入错了,想要编辑,可以双击该待办事项,进入编辑输入框状态,并让输入框获取焦点(这个有些难,需要一个自定义指令),把原来待办事项回显到输入框中,然后可以编辑并保存。如果清空了,按回车则是删除该todo 总结:
- 双击待办事项,展示编辑文本框
- 按回车或者编辑文本框失去焦点,修改数据
- 按esc取消编辑
- 把编辑文本框清空按回车,删除这一项
- 显示编辑文本框的时候获取焦点
// 3. 编辑待办项
const useEdit= remove => {
// 用于缓存编辑之前的内容,如果取消编辑则使用改值,这个值不影响dom更新,不需要ref
let beforeEditingText = ''
// 编辑内容,绑定编辑输入文本框
const editingTodo = ref(null)
const editTodo = todo => {
beforeEditingText = todo.text;
editingTodo.value = todo
}
// 如果编辑时,内容全部清空则为删除
const doneEdit = todo => {
if(!editingTodo.value) return
todo.text = todo.text.trim()
todo.text || remove(todo) // 删除
editingTodo.value = null
}
// 取消编辑方法
const cancelEdit = todo => {
editingTodo.value = null
todo.text = beforeEditingText
}
return {
editingTodo,
editTodo,
doneEdit,
cancelEdit
}
}
这里有remove方法需要传进来。找到遍历相关的位置,绑定编辑状态,绑定双击编辑事件对应方法editTodo。输入文本框绑定焦点事件,doneEdit方法,绑定esc事件绑定cancelEdit方法。
<ul class="todo-list">
<li
v-for="todo in todos"
:key="todo" // 这里的key最好绑定todo对象
:class="{ editing: todo === editingTodo }" // 编辑状态隐藏viewdom
>
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{ todo.text }}</label> // 绑定双击,editTodo
<button class="destroy" @click="remove(todo)"></button>
</div>
<input
class="edit"
type="text"
v-editing-focus="todo === editingTodo"
v-model="todo.text"
@keyup.enter="doneEdit(todo)" // 绑定enter事件,删除todo
@blur="doneEdit(todo)"
@keyup.esc="cancelEdit(todo)"// 取消编辑
>
</li>
</ul>
注意: 当双击文本框怎样让文本框出现光标,接下来处理
双击出现文本框,让文本框获取焦点
自定义指令
- 第二个参数是对象时:
vue 2.x
Vue.directive('editingFocus', {
bind(el, binding,vnode, prevVnode) {},
inserted() {},
update() {},
componentUploadtes() {},
unbind() {}
})
vue 3.0
Vue.directive('editingFocus', {
beforeMount(el, binding,vnode, prevVnode) {},
mounted() {},
beforeUpdate() {},
updated() {},
beforeUnmount() {},
unmounted() {}
})
两者查边是自定指令钩子函数被重命名,vue 3指令钩子和组件钩子函数保持一致,这样更容易理解,降低使用的心智负担。但是自定义指令钩子函数与组件钩子函数执行方式有些不同(什么鬼...)
- 第二个参数是函数时,vue2.x 和 vue3没有区别
app.directive('editingFocus', (el, binding) => {
binding.value && el.focus()
})
我们可以使用自定义指令是操作文本框的焦点,我们自定义指定名字就叫 v-editing-focus
<input
class="edit"
type="text"
v-editing-focus="todo === editingTodo"
v-model="todo.text"
@keyup.enter="doneEdit(todo)"
@blur="doneEdit(todo)"
@keyup.esc="cancelEdit(todo)"
>
directives: {
editingFocus: (el, binding) => {
binding.value && el.focus()
}
}
这样当绑定的值为true则让改元素触发焦点事件
完成三分之二,明天继续....