9. 底部待做事项状态统计
App.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <!-- 将添加待做事项的方法传入子组件中 --> <TodoAddTask :addTodo="addTodo"></TodoAddTask> <!-- 将数据和方法传入子组件 --> <TodoList :todos="todos" :changeDone="changeDone" :deleteTodo="deleteTodo" ></TodoList> <TodoSituation :todos="todos"></TodoSituation> </div> </div> </template>
TodoSituation.vue
<template> <div class="todo-footer"> <label> <input type="checkbox" /> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{todos.length}} </span> <button class="btn btn-danger">清除已完成任务</button> </div> </template> <script> export default { name: 'TodoSituation', props: ['todos'], computed: { doneTotal() { // 对todos进行遍历统计 // pre为上一次的返回值 // todo为当前的遍历项 return this.todos.reduce((pre, todo) => { return pre + (todo.done ? 1 : 0) }, 0) } } } </script>
10. 全选 & 清除已完成
App.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <!-- 将添加待做事项的方法传入子组件中 --> <TodoAddTask :addTodo="addTodo"></TodoAddTask> <!-- 将数据和方法传入子组件 --> <TodoList :todos="todos" :changeDone="changeDone" :deleteTodo="deleteTodo" ></TodoList> <TodoSituation :todos="todos" :checkAll="checkAll" :deleteDone="deleteDone" ></TodoSituation> </div> </div> </template> <script> // 导入子组件 import TodoAddTask from './components/TodoAddTask.vue' import TodoList from './components/TodoList.vue' import TodoSituation from './components/TodoSituation.vue' export default { name: 'App', components: { TodoAddTask, TodoList, TodoSituation }, data() { return { todos: [ {id: '001', todo: '吃饭', done: true}, {id: '002', todo: '睡觉', done: false}, {id: '003', todo: '打豆豆', done: true} ] } }, methods: { // 将新的待做事项添加到列表中 addTodo(todo) { this.todos.unshift(todo) }, // 修改待做事项的勾选状态 changeDone(id) { this.todos.forEach((todo)=>{ if (todo.id === id) todo.done = !todo.done }) }, // 删除todo deleteTodo(id) { this.todos = this.todos.filter((todo)=>{ return todo.id !== id }) }, // 全选 全不选 checkAll(done) { this.todos.forEach((todo)=>{ todo.done = done }) }, // 清除已完成 deleteDone() { this.todos = this.todos.filter(todo => !todo.done) } }, } </script>
TodoSituation.vue
<template> <div class="todo-footer" v-show="total"> <label> <input type="checkbox" v-model="isAll"/> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{total}} </span> <button class="btn btn-danger" @click="clearDone">清除已完成任务</button> </div> </template> <script> export default { name: 'TodoSituation', props: ['todos', 'checkAll', 'deleteDone'], computed: { // 计算总的待做事项数 total() { return this.todos.length }, // 计算打钩的待做事项数 doneTotal() { return this.todos.reduce((pre, todo) => { return pre + (todo.done ? 1 : 0) }, 0) }, // 是否全部勾选 isAll: { get() { return this.total === this.doneTotal && this.total >0 }, set(value) { this.checkAll(value) } } }, methods: { // 清除已完成 clearDone() { this.deleteDone() } }, } </script>
11. 总结
- 组件化编码流程:
- (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
- (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
- 1).一个组件在用:放在组件自身即可。
- 2). 一些组件在用:放在他们共同的父组件上(
状态提升
)。
- (3).实现交互:从绑定事件开始。
- props适用于:
- (1).父组件 ==> 子组件 通信
- (2).子组件 ==> 父组件 通信(要求父先给子一个函数)
- 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
- props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
12. 实现本地存储
App.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <!-- 将添加待做事项的方法传入子组件中 --> <TodoAddTask :addTodo="addTodo"></TodoAddTask> <!-- 将数据和方法传入子组件 --> <TodoList :todos="todos" :changeDone="changeDone" :deleteTodo="deleteTodo"></TodoList> <TodoSituation :todos="todos" :checkAll="checkAll" :deleteDone="deleteDone"></TodoSituation> </div> </div> </template> <script> // 导入子组件 import TodoAddTask from './components/TodoAddTask.vue' import TodoList from './components/TodoList.vue' import TodoSituation from './components/TodoSituation.vue' export default { name: 'App', components: { TodoAddTask, TodoList, TodoSituation }, data() { return { // todos的值从本地存储中进行获取 // 如果本地存储不存在todos会返回null,此时todos的值为空数组 // 当读取本地存储返回null,null为假,会返回 [] // 由于存储的为json字符串,需要进行类型转换 todos: JSON.parse(localStorage.getItem('todos')) || [] } }, methods: { // 将新的待做事项添加到列表中 addTodo(todo) { this.todos.unshift(todo) }, // 修改待做事项的勾选状态 changeDone(id) { this.todos.forEach(todo => { if (todo.id === id) todo.done = !todo.done }) }, // 删除todo deleteTodo(id) { this.todos = this.todos.filter(todo => { return todo.id !== id }) }, // 全选 全不选 checkAll(done) { this.todos.forEach(todo => { todo.done = done }) }, // 清除已完成 deleteDone() { this.todos = this.todos.filter(todo => !todo.done) } }, watch: { // 使用监视属性监视todos的改变 // 只要todos发生了改变,就将新的todos进行本地存储 todos: { // 由于默认只会监视一层,监视数组内对象中是否完成的变化需要进行深度监视 deep: true, handler(newVal) { // 由于todos是数组对象类型数据,进行本地存储需要转换为json localStorage.setItem('todos', JSON.stringify(newVal)) } } } }
13. 自定义事件实现子组件向父组件传递数据
App.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <!-- 为子组件TodoAddTask绑定自定义事件addTodo,事件的回调函数为addTodo() --> <TodoAddTask @addTodo="addTodo"></TodoAddTask> <!-- 将数据和方法传入子组件 --> <TodoList :todos="todos" :changeDone="changeDone" :deleteTodo="deleteTodo"></TodoList> <!-- 为子组件 TodoSituation 绑定自定义事件checkAll deleteDone --> <TodoSituation :todos="todos" @checkAll="checkAll" @deleteDone="deleteDone"></TodoSituation> </div> </div> </template> <script> // 导入子组件 import TodoAddTask from './components/TodoAddTask.vue' import TodoList from './components/TodoList.vue' import TodoSituation from './components/TodoSituation.vue' export default { name: 'App', components: { TodoAddTask, TodoList, TodoSituation }, data() { return { // todos的值从本地存储中进行获取 // 如果本地存储不存在todos会返回null,此时todos的值为空数组 // 当读取本地存储返回null,null为假,会返回 [] // 由于存储的为json字符串,需要进行类型转换 todos: JSON.parse(localStorage.getItem('todos')) || [] } }, methods: { // 将新的待做事项添加到列表中 addTodo(todo) { this.todos.unshift(todo) }, // 修改待做事项的勾选状态 changeDone(id) { this.todos.forEach(todo => { if (todo.id === id) todo.done = !todo.done }) }, // 删除todo deleteTodo(id) { this.todos = this.todos.filter(todo => { return todo.id !== id }) }, // 全选 全不选 checkAll(done) { this.todos.forEach(todo => { todo.done = done }) }, // 清除已完成 deleteDone() { this.todos = this.todos.filter(todo => !todo.done) } }, watch: { // 使用监视属性监视todos的改变 // 只要todos发生了改变,就将新的todos进行本地存储 todos: { // 由于默认只会监视一层,监视数组内对象中是否完成的变化需要进行深度监视 deep: true, handler(newVal) { // 由于todos是数组对象类型数据,进行本地存储需要转换为json localStorage.setItem('todos', JSON.stringify(newVal)) } } } } </script>
TodoAddTask.vue
<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="task" @keydown.enter="addTask" /> </div> </template> <script> // 导入nanoid import {nanoid} from 'nanoid' export default { name: 'TodoAddTask', // 使用自定义事件,不用再接收父组件传递的函数 // props: ['addTodo'], data() { return { task: '' } }, methods: { addTask() { // 没有输入不进行添加 if (!this.task) return // 新的待做事项 const todo = { id: nanoid(), todo: this.task, done: false } // 触发自定义事件 添加待做事项 // this.addTodo(todo) this.$emit('addTodo', todo) // 输入框清空 this.task = '' } } } </script>
TodoSituation.vue
<template> <div class="todo-footer" v-show="total"> <label> <input type="checkbox" v-model="isAll"/> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{total}} </span> <button class="btn btn-danger" @click="clearDone">清除已完成任务</button> </div> </template> <script> export default { name: 'TodoSituation', // props: ['todos', 'checkAll', 'deleteDone'], // 使用自定义事件,不用再接收父组件传递的函数 props: ['todos'], computed: { // 计算总的待做事项数 total() { return this.todos.length }, // 计算打钩的待做事项数 doneTotal() { return this.todos.reduce((pre, todo) => { return pre + (todo.done ? 1 : 0) }, 0) }, // 是否全部勾选 isAll: { get() { return this.total === this.doneTotal && this.total >0 }, set(value) { // this.checkAll(value) // 触发自定义事件 选择所有 this.$emit('checkAll', value) } } }, methods: { // 清除已完成 clearDone() { // this.deleteDone() // 触发自定义事件 清除已完成 this.$emit('deleteDone') } }, } </script>