文章内容输出来源:拉勾教育大前端高薪训练营
1. ToDoList功能列表
- 添加待办事项
- 删除待办事项
- 编辑待办事项
- 切换待办事项
- 存储待办事项
2.项目构建
使用vue脚手架创建项目,先升级vue-cli到4.5以上的版本,新版本的vue-cli创建项目时可以选择vue版本
npm update -g @vue/cli
# OR
yarn global upgrade --latest @vue/cli
如果已经全局安装了旧版本的vue-cli(1.x或者2.x), 你需要先通过
npm uninstall vue-cli -g
或yarn global remove vue-cli
卸载它,然后再使用npm install -g @vue/cli
或yarn global add @vue/cli
安装新的包 然后就可以使用vue create
创建项目了:
vue create 04-todolist
然后选择Default (Vue 3 Preview) ([Vue 3] babel, eslint)
就会自动创建项目了
3. 添加待办事项
在输入框输入文本内容按下enter键添加待办事项
// 1.添加待办
const useAdd = todos => {
const input = ref('')
const addTodo = () => {
const text = input.value && input.value.trim()
if (text.length === 0) return
todos.value.unshift({
text,
completed: false
})
input.value = ''
}
return {
input,
addTodo
}
}
4.删除待办
点击待办事项右侧的叉号删除待办事项
// 2.删除待办
const useRemove = todos => {
const remove = todo => {
const index = todos.value.indexOf(todo)
todos.value.splice(index, 1)
}
return {
remove
}
}
5.编辑待办事项
双击进入编辑状态,按esc退出编辑,按enter提交编辑,如果删光了文本,则删除这一项。
- 双击待办事项,展示编辑文本框
- 按回车或者编辑文本框失去焦点,修改数据
- 按esc取消编辑
- 把编辑文本框清空按回车,删除这一项
- 显示编辑文本框的时候获取焦点
// 3.编辑待办
const useEdit = (remove) => {
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
}
}
模板中:
<li
v-for="todo in todos"
:key="todo"
:class="{ editing: todo === editingTodo }"
>
<div class="view">
<input class="toggle" type="checkbox">
<label @dblclick="editTodo(todo)">{{ todo.text }}</label>
<button class="destroy" @click="remove(todo)"></button>
</div>
<input
type="text"
class="edit"
v-model="todo.text"
@keyup.enter="doneEdit(todo)"
@blur="doneEdit(todo)"
@keyup.esc="cancelEdit(todo)"
>
</li>
6.编辑文本框获取焦点-vue3.0自定义指令
传对象形式:
- Vue2.x
Vue.directive('editingFocus', {
bind(el,binding, vnode, prenode) {},
inserted() {},
updatew() {}, // remove
componentUpdated() {},
unbind() {}
})
- Vue3.0
app.directive('editingFocus', {
beforeMount(el, binding, vnode, prevnode) {},
mounted() {},
beforeUpdate() {},
updated() {},
beforeUnmount() {}, // new
unmounted() {}
})
传函数形式:
- Vue2.x
Vue.directive('editingFocus', (el, binding) => {
binding.value && el.focus()
})
- Vue3.0(函数在bind和updated之间执行,el是指令所绑定的元素,binding.value是指令对应的值)
app.directive('editingFocus',(el, binding) => {
binding.value && el.focus()
})
代码实现自定义事件获取正在编辑的文本框焦点:
export default {
name: 'App',
setup() {
const todos = ref([])
const { remove } = useRemove(todos) // 编辑待办事项需要使用到remove函数,所以单独解构出来
return {
todos,
remove,
...useAdd(todos),
...useEdit(remove)
}
},
directives: {
editingFocus: (el, binding) => {
binding.value && el.focus()
}
}
}
使用:
<input
type="text"
class="edit"
v-model="todo.text"
v-editing-focus="todo === editingTodo" // 自定义指令
@keyup.enter="doneEdit(todo)"
@blur="doneEdit(todo)"
@keyup.esc="cancelEdit(todo)"
>
7. 切换待办事项
- 点击CheckBox可以改变所有待办项状态
- All/Active/Completed
- 其他
- 显示未完成待办项个数
- 移除所有完成的项目
- 如果没有待办项,隐藏main和footer
// 2.删除待办
const useRemove = todos => {
const remove = todo => {
const index = todos.value.indexOf(todo)
todos.value.splice(index, 1)
}
const removeCompleted = () => { // 移除所有完成的项目,需要再setup中解构并导出
todos.value = todos.value.filter(todo => !todo.completed)
}
return {
remove,
removeCompleted
}
}
// 4.切换待办事项的完成状态
const useFilter = todos => {
const allDone = computed({ // 全选切换
get() {
return todos.value.every(todo => todo.completed)
},
set(value) {
todos.value.forEach(todo => {
todo.completed = value
})
}
})
const filter = { // 过滤待办事项的函数集
all: list => list,
active: list => list.filter(todo => !todo.completed),
completed: list => list.filter(todo => todo.completed)
}
const type = ref('all')
const filteredTodos = computed(() => filter[type.value](todos.value)) // 过滤之后的待办事项
const remainingCount = computed(() => filter.active(todos.value).length) // 剩余待办数量
const count = computed(() => todos.value.length) // 待办事项总数量
const toggleFilterType = filterType => { // 切换过滤类型
type.value = filterType
}
return {
allDone,
toggleFilterType,
filteredTodos,
remainingCount,
count
}
}
使用:
<footer class="footer" v-show="count">
<span class="todo-count">
<strong>{{ remainingCount }}</strong> {{ remainingCount > 1 ? 'items' : 'item'}} left
</span>
<ul class="filters">
<!-- <li><a href="#/all">All</a></li>
<li><a href="#/active">Active</a></li>
<li><a href="#/completed">Completed</a></li> -->
<li @click="toggleFilterType('all')"><a>All</a></li>
<li @click="toggleFilterType('active')"><a>Active</a></li>
<li @click="toggleFilterType('completed')"><a>Completed</a></li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="count > remainingCount">
Clear completed
</button>
</footer>
8.存储待办事项
import useLocalStorage from './utils/useLocalStorage'
const storage = useLocalStorage()
// 5.存储待办事项
const useStorage = () => {
const KEY = 'TODOKEYS'
const todos = ref(storage.getItem(KEY) || [])
watchEffect(() => {
storage.setItem(KEY, todos.value)
})
return todos
}
utils/useLocalStorage.js
function parse(str) {
let value
try {
value = JSON.parse(str)
} catch {
value = null
}
return value
}
function stringify(obj) {
let value
try {
value = JSON.stringify(obj)
} catch {
value = null
}
return value
}
export default function useLocalStorage() {
function setItem(key, value) {
value = stringify(value)
window.localStorage.setItem(key, value)
}
function getItem(key) {
let value = window.localStorage.getItem(key)
if (value) {
value = parse(value)
}
return value
}
return {
setItem,
getItem
}
}