承接昨天编辑todo,接下来切换代办事项状态,对todolist进行完成,和未完成状态进行筛选!
切换代办事项状态
这件事情还是比较麻烦,分好几个子任务
分析:
第一个子任务,点击左上角的折线(checkbox,我们点击的是checkbox的label,为了美观这个checkbox不让他显示出来,给他一像素安排,点击label的时候就是点击checkbox,小妙招。),会对所有的待办事项标记完成或者未完成
第二个子任务:
- 点击Active的时候会显示未完成的待办事项,
- 点击Completed的时候会显示已完成的待办事项,
- 点击All的时候会像是所有的待办事项,
第三个子任务:
- 左下角显示条目数,
- 右下角的按钮是清除已完成待办事项,
- 当条目数为空时,隐藏线面操作栏
1. 子任务一:点击checkbox,改变所有待办事项完成状态
设置checkbox的id为toggle-all,然后label 的 for绑定checkbox id。完成点击label就是点击checkbox的操作。小技巧 label 绑定 input
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
复制代码
这个全选的checkbox会做两件事,一是他的是否选中状态,和todos里面所有待办事项的选中状态相关,这就是todos状态会影响全选状态,所以这里的全选状态(allDone)可以使用计算属性。二是,这个全选状态的变化,还会去设置todos里面的状态,所以说我们这里可以用到我们计算属性get set的写法。完成交互
这个全选在写很多全选控制的交互有很多,以后大家记得使用计算属性来做。checkbox table等
知识点: computed (get set) 使用场景
const useFilter = todos => {
// 声明全选状态计算属性 allDone
const allDone = computed({
get () {
// 当todos全部为true时u,计算属性为true
return !todos.value.filter(todo => !todo.completed).length
},
set (value) {
// 当计算属性被设置时,同步todos里面所有状态与计算属性保持一致
todos.value.forEach(todo => {
todo.completed = value
})
}
})
return {
allDone
}
}
复制代码
实现一个小细节,当待办事项选中是,todo文字加一条删除线,设置一个complated类,是否有此类由每条todo的complated的属性值决定,实现样式可以去源码查看实现
知识点: ** v-for,绑定class**
···
<ul class="todo-list">
<li
v-for="todo in todos"
:key="todo"
:class="{ editing: todo === editingTodo, completed: todo.completed }" // 这里使用complated类画横线
>
···
复制代码
2. 子任务二:点击all active complated三个超链接的筛选
思路: 把过滤待办事项的函数定义到一个对象中,然后根据hash的值去对象中取不同状态的数据,当然对象的函数名称和hash值是一样的,这样就避免使用过多的if语句。
知识点:
- computed, ref, onMounted注册事件 onUnmounted注销事件
- 使用方法名和状态key有关联关系,也是一个值得学习的小技巧,当处于什么状态就可以直接执行该方法不需要太多if判断,很值得学习
// 注册响应式数据:筛选状态类型,
const type = ref('all')
// 当类型改变,计算属性filteredTodos计算属性跟着发生改变
const filteredTodos = computed(() => filter[type.value](todos.value))
// 定义hash变化处理函数
const onHashChange = () => {
// 获取地址的hash值
const hash = window.location.hash.replace('#/', '')
// 当hash在filter对象中有值则赋值给type,没有则显示全部,因为链接上hash可以随便输入所以有一个if判断
if(filter[hash]) {
type.value = hash
} else {
type.value = 'all'
window.location.hash = ''
}
}
// 页面首次加载的时候,也要去获取onHashChange里面的数据,并注册hashchange事件
onMounted(() => {
window.addEventListener('hashchange', onHashChange)
onHashChange()
})
// 组件卸载时注销事件
onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
复制代码
3. 子任务三,
-
显示未完成待办事项的个数,
定义一个计算属性remainingCount,获得filter.active的长度即可
const remainingCount = computed(() => filter.active(todos.value).length) // 显示在左下角 <strong>{{ remainingCount }}</strong> {{ remainingCount > 1 ? 'items' : 'item' }} left 复制代码
-
点击按钮删除已完成的事项,
给点击按钮注册点击事件,点击的时候执行清除已完成事件方法removeCompleted,还有一个小功能,当待办事项里面没有已完成事项时,清除按钮不显示。使用v-show绑定数据控制按钮显示和隐藏。
因为这个操作也是删除操作,所以应该在useRemove里面编写代码
const useRemove = todos => {
···
const removeCompleted = () => {
todos.value = todos.value.filter(todo => !todo.completed)
}
return {
remove,
removeCompleted
}
}
复制代码
- 如果没有待办事项就只显示待办事项文本框 和 如果没有已完成的待办事项 清除已完成按钮也要隐藏
计算属性count表示所有待办事项条目
使用v-show绑定计算属性count 隐藏main 和 footer,如果count > remianingCount则表示待办事项中有已完成待办事项,显示清除按钮
知识点: v-show
<section class="main" v-show="count">
···
<footer class="footer" v-show="count">
···
<button class="clear-completed" v-show="count > remainingCount" @click="removeCompleted">
Clear completed
</button>
复制代码
最后一个任务,把todoList存储到localStorage里面
我们可以创建一个utils文件夹,创建一个useLocalStorage.js文件,对localStorage进行封装,方便使用!
知识点: use函数,模块抽取,方便更多的组件复用
// useLocalStorage.js
// 转化为json对象
function parse (str) {
let value
try {
value = JSON.parse(str)
} catch {
value = null
}
return value
}
// 转化为json字符串
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
}
}
复制代码
定义完工具函数,我们应该怎么使用呢?
我们知道在添加,删除,编辑待办事项todos都会发生变化,如果我们在这些use函数里面都使用存储操作,这样代码耦合性太高,太麻烦了,还记得第一天学习的watchEffect么,对,可以使用它,监听todos变化,进行数据存储。
import useLocalStorage from './utils/useLocalStorage'
···
const storage = useLocalStorage()
const useStorage = () => {
const KEY = 'TODOKEYS'
// 在本地存储中取数据完成数据todos的初始化
const todos = ref(storage.getItem(KEY) || [])
// 当todos变化事存储todos
watchEffect(() => {
storage.setItem(KEY, todos.value)
})
return todos
}
复制代码
总结
项目Todolist: github.com/zelixag/tod…
使用一个简单的todolist的需求,对vue核心API ref, computed, watchEffect,computed的get set使用场景等等…的熟悉,最关键的是compositionAPI的使用,我们使用五个use函数完成todolist的需求,践行compositionAPI思想。我们把不同的功能分别放到不同函数中去实现,这样代码逻辑更清晰。最后还做了一个useLocalStorage,把use函数封装到了一个单独的模块中,将来可以在其他组件中重用。你可以看一下下面compositionAPI 和 optionsApi对比图,哪个代码结构清晰,好维护一幕了然。
我们把不同逻辑的代码拆分到use 函数中,同一功能代码放到一个函数中,而且更方便组件之间重用代码,这是compositionAPI比options好的地方,好了todolist的结束了,vue学习第三天,感受vue的使用的方便就到这,接下来我们去认识一下 vue3的响应式系统原理,敬请期待!!!