饮水思源:https://www.bilibili.com/video/BV1Zy4y1K7SH?p=75
①底部统计
MyFooter.vue:
<template> <div> <label> <input type="checkbox" /> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{todos.length}} </span> <button>清除已完成项目</button> </div> </template> <script> export default { name: 'MyFooter', props: ['todos'], computed: { doneTotal() { return this.todos.reduce((pre, current) => { if (current.done) return pre + 1; return pre; }, 0) } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
②底部交互
MyFooter.vue:
<template> <div v-show="total"> <label> <input type="checkbox" :checked="allDone" @click="CheckAllTodo"/> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{total}} </span> <button>清除已完成项目</button> </div> </template> <script> export default { name: 'MyFooter', props: ['todos', 'appCheckAllTodo'], computed: { total() { return this.todos.length }, doneTotal() { return this.todos.reduce((pre, current) => { if (current.done) return pre + 1; return pre; }, 0) }, allDone() { return this.total > 0 && this.doneTotal === this.total }, }, methods: { CheckAllTodo(e) { this.appCheckAllTodo(e.target.checked) } }, } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
v-model配合计算属性set实现:
<template> <div v-show="total"> <label> <input type="checkbox" v-model="allDone" /> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{total}} </span> <button>清除已完成项目</button> </div> </template> <script> export default { name: 'MyFooter', props: ['todos', 'appCheckAllTodo'], computed: { total() { return this.todos.length }, doneTotal() { return this.todos.reduce((pre, current) => { if (current.done) return pre + 1; return pre; }, 0) }, allDone: { get() { return this.total > 0 && this.doneTotal === this.total }, set(val) { this.appCheckAllTodo(val) } }, }, } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
③总结TodoList案例
饮水思源:https://www.bilibili.com/video/BV1Zy4y1K7SH?p=77&spm_id_from=pageDriver
④本地存储
深度检测:为了发现对象内部值的变化,可以在选项参数中指定 deep: true。
查看本地存储:
思路就是,检测todos,每当todos改变时,将todos保存到localStorage,每当启动app,就从localStorage读出数据进行初始化,几个注意点:
- 要把list转化为字符串存储,取出时进行字符串解析
- 在list中没数据时,或者第一次启动app时,注意localStorage.getItem返回null的问题
- 处理无法保存勾选状态的现象——启用深度检测
App.vue:
<template> <div> <MyHeader :appAddItem="appAddItem" /> <MyList :todos="todos" :appRemoveTodo="appRemoveTodo" /> <MyFooter :todos="todos" :appCheckAllTodo="appCheckAllTodo" /> </div> </template> <script> import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name: 'App', data() { return { todos: JSON.parse(localStorage.getItem('todos')) || [] // 初始化时调用一次 } }, watch: { todos: { deep: true, handler(newVal) { localStorage.setItem('todos', JSON.stringify(newVal)) }, }, }, methods: { appAddItem(x) { this.todos.unshift(x); }, appRemoveTodo(todoId) { this.todos = this.todos.filter(todo => todo.id !== todoId); }, appCheckAllTodo(done) { this.todos.forEach(todo => { todo.done = done }) }, }, components: { MyHeader, MyList, MyFooter, } } </script> <style> </style>
④用自定义事件替代props从子组件向父组件传递数据
MyHeader向App传数据。
App向MyHeader绑定事件并传入回调函数(除了写在标签里外,还有更灵活的方式,详见视频):
<template> <div> <MyHeader @appAddItem="appAddItem" /> <MyList :todos="todos" :appRemoveTodo="appRemoveTodo" /> <MyFooter :todos="todos" :appCheckAllTodo="appCheckAllTodo" /> </div> </template> <script> import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name: 'App', data() { return { todos: JSON.parse(localStorage.getItem('todos')) || [] // 初始化时调用一次 } }, watch: { todos: { deep: true, handler(newVal) { localStorage.setItem('todos', JSON.stringify(newVal)) }, }, }, methods: { appAddItem(x) { this.todos.unshift(x); }, appRemoveTodo(todoId) { this.todos = this.todos.filter(todo => todo.id !== todoId); }, appCheckAllTodo(done) { this.todos.forEach(todo => { todo.done = done }) }, }, components: { MyHeader, MyList, MyFooter, } } </script> <style> </style>
MyHeader中触发事件:
<template> <input type="text" placeholder="输入任务,按回车确认" @keyup.enter="addItem"/> </template> <script> import {nanoid} from 'nanoid' export default { name: 'MyHeader', methods: { addItem(e) { const todoObj = { id: nanoid(), title: e.target.value, done: false, } // 触发绑定在自己身上的appAddItem事件,会导致调用父组件传入的回调函数 this.$emit('appAddItem', todoObj) }, }, } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
通过开发者工具查看已触发的自定义事件: