7.4 实现添加任务的功能
1.在 App.vue 组件中监听 TodoInput 组件自定义的 add 事件:
<!-- 使用TodoInput组件 --> <todo-input @add="onAddNewTask"></todo-input>
2.在 App.vue 组件的 data 中声明 nextId 来模拟 id 自增 +1 的操作:
data() { return { // 任务列表的数据 todolist: [ { id: 1, task: '周一早晨9点开会', done: false }, { id: 2, task: '周一晚上8点聚餐', done: false }, { id: 3, task: '准备周三上午的演讲稿', done: true }, ], nextId: 4 } },
3.在 App.vue 组件的 methods 中声明 onAddNewTask 事件处理函数如下:
methods: { onAddNewTask( taskname ) { this.todolist.push( { id: this.nextId, task: taskname, done: false } ) this.nextId++ } },
8. 封装 todo-button 组件
8.1 创建并注册 TodoButton 组件
1.在 src/components/todo-button/ 目录下新建 TodoButton.vue 组件:
<template> <div> <h3>TodoButton</h3> </div> </template> <script> export default { name: 'TodoButton' } </script> <style> </style>
2.在 App.vue 组件中导入并注册 TodoButton.vue 组件:
<template> <div> <h1>App 组件</h1> <hr> <!-- 使用TodoInput组件 --> <todo-input @add="onAddNewTask"></todo-input> <!-- 使用TodoList组件 --> <todo-list :list="todolist"></todo-list> <!-- 使用TodoButton组件 --> <todo-button></todo-button> </div> </template> <script> import TodoList from './components/todo-list/TodoList.vue' import TodoInput from './components/todo-input/TodoInput.vue' import TodoButton from './components/todo-button/TodoButton.vue' export default { name: 'App', data() { return { // 任务列表的数据 todolist: [ { id: 1, task: '周一早晨9点开会', done: false }, { id: 2, task: '周一晚上8点聚餐', done: false }, { id: 3, task: '准备周三上午的演讲稿', done: true }, ], nextId: 4 } }, methods: { onAddNewTask( taskname ) { this.todolist.push( { id: this.nextId, task: taskname, done: false } ) this.nextId++ } }, components: { TodoList, TodoInput, TodoButton } } </script> <style lang="less" scoped> </style>
8.2 基于 bootstrap 渲染组件结构
1.根据 bootstrap 提供的 Button group(https://v4.bootcss.com/docs/components/button-group/)渲染 Todobutton 组件的基本结构。
2.在 TodoButton 组件中渲染如下的 DOM 结构:
<template> <div> <div class="btn-group" role="group" aria-label="Basic example"> <button type="button" class="btn btn-secondary">全部</button> <button type="button" class="btn btn-secondary">已完成</button> <button type="button" class="btn btn-secondary">未完成</button> </div> </div> </template> <script> export default { name: 'TodoButton' } </script> <style> </style>
3.并通过 button-container 类名美化组件的样式:
<template> <div class="button-container"> <div class="btn-group" role="group" aria-label="Basic example"> <button type="button" class="btn btn-secondary">全部</button> <button type="button" class="btn btn-secondary">已完成</button> <button type="button" class="btn btn-secondary">未完成</button> </div> </div> </template> <script> export default { name: 'TodoButton' } </script> <style lang="less" scoped> .button-container { width: 400px; text-align: center; } </style>
8.3 通过 props 指定默认激活的按钮
1.在 TodoButton 组件中声明如下的 props,用来指定默认激活的按钮的索引:
<script> export default { name: 'TodoButton', props: { active: { type: Number, required: true, // 默认激活索引为0的按钮 // 0 全部,1 已完成,2 未完成 default: 0 } } } </script>
2.通过 动态绑定 class 类名 的方式控制按钮的激活状态:
<template> <div class="button-container"> <div class="btn-group" role="group" aria-label="Basic example"> <button type="button" class="btn" :class="active===0 ? 'btn-primary' : 'btn-secondary'" >全部</button> <button type="button" class="btn" :class="active===1 ? 'btn-primary' : 'btn-secondary'" >已完成</button> <button type="button" class="btn" :class="active===2 ? 'btn-primary' : 'btn-secondary'" >未完成</button> </div> </div> </template>
3.在 App 组件中声明默认激活项的索引,并通过属性绑定的方式传递给 TodoButton 组件:
nextId: 4, activeBtnIndex: 0
<!-- 使用TodoButton组件 --> <todo-button :active="activeBtnIndex"></todo-button>
8.4 通过 v-model 更新激活项的索引
需求分析:
父 -> 子 通过 props 传递了激活项的索引(active) 子 -> 父 需要更新父组件中激活项的索引
这种场景下适合在组件上使用 v-model 指令,维护组件内外数据的同步。
1.为 TodoButton 组件中的三个按钮分别绑定 click 事件处理函数如下:
<template> <div class="button-container"> <div class="btn-group" role="group" aria-label="Basic example"> <button type="button" class="btn" :class="active===0 ? 'btn-primary' : 'btn-secondary'" @click="onbtnClick(0)" >全部</button> <button type="button" class="btn" :class="active===1 ? 'btn-primary' : 'btn-secondary'" @click="onbtnClick(1)" >已完成</button> <button type="button" class="btn" :class="active===2 ? 'btn-primary' : 'btn-secondary'" @click="onbtnClick(2)" >未完成</button> </div> </div> </template>
2.在 TodoButton 组件中声明如下的自定义事件,用来更新父组件通过 v-model 指令传递过来的 props 数据:
<script> export default { name: 'TodoButton', emits: ['update:active'], props: { active: { type: Number, required: true, // 默认激活索引为0的按钮 // 0 全部,1 已完成,2 未完成 default: 0 } } } </script>
3.在 TodoButton 组件的 methods 节点中声明 onBtnClick 事件处理函数如下:
methods: { onbtnClick( index ) { // 1. 如果当前点击的按钮的索引值,等于 props 传递过来的索引值,则没必要触发 update:active 自定义事件 if (index === this.active) return // 2. 通过 this.$emit() 方法触发自定义事件 this.$emit('update:active', index) } }
<!-- 使用TodoButton组件 --> <todo-button v-model:active="activeBtnIndex"></todo-button>
8.5 通过计算属性动态切换列表的数据
点击不同的按钮,切换显示不同的列表数据。此时可以根据当前激活按钮的索引,动态计算出要显示的列表数据并返回即可!
1.在 App 根组件中声明如下的计算属性:
computed: { // 根据激活按钮的索引值,动态计算要展示的列表数据 tasklist() { // 对“源数据”进行 switch...case 的匹配,并返回“计算之后的结果” switch (this.activeBtnIndex) { case 0: return this.todolist case 1: return this.todolist.filter(x => x.done) case 2: return this.todolist.filter(x => !x.done) } } },
2.在 App 根组件的 DOM 结构中,将 TodoList 组件的 :list=“todolist” 修改为:
<!-- 使用TodoList组件 --> <todo-list :list="tasklist"></todo-list>
9. 最终效果