文章目录
- 组件化编码流程(通用)
- 1. 拆分静态组件
- 2. 初始化列表
- 展示动态数据
- 如何让一个标签动态的拥有某一个属性
- 3. 按回车添加todo
- 子组件给父组件传值之props
- 4. 勾选与取消勾选一个Todo
- 5. 删除
- 6. footer底部统计
- 7. footer底部交互
- 7.1 全选框自动打勾
- 7.2 全选框取消勾选
- 8. 清除已完成任务
- 总结
组件化编码流程(通用)
1、实现静态组件:抽取组件,使用组件实现静态页面效果
2、展示动态数据:
- 数据的类型、名称是什么
- 数据保存再哪个组件
3、交互–从绑定事件监听开始
如果组件名字不好起,想一下是不是组件拆分的不合理
1. 拆分静态组件
根据功能,可划分为以下几个组件:
将已写好页面的html,css根据组件进行拆分,得到的文件结构如下:
App组件里使用了MyHeader、MyList、MyFooter组件
MyList组件里使用了MyItem组件:
2. 初始化列表
展示动态数据
(1) 确定数据名称、数据类型
数据名称:这一堆的代办事项数据名称可以叫todos,
数据类型:最好用数组存储这一堆的待办事项,而每一个代办事项可以用对象存储。
(2) 数据保存在哪个组件:
谁用这些数据,就写在哪个组件里。后续如果有变化,再改变位置。因此暂时写在MyList组件里。
MyList.vue
<template>
<ul class="todo-main">
<!-- 循环生成多个MyItem,且将具体的代办事项传递给MyItem,否则还是页面的列表里还是yyyy-->
<MyItem v-for="toboObj in todos" :key="toboObj.id" :todo="toboObj" />
</ul>
</template>
<script>
...
data () {
return {
todos: [
{ id: '0001', title: '吃饭', done: true },
{ id: '0002', title: '睡觉', done: true },
{ id: '0003', title: '打豆豆', done: false }]
}
}
</script>
MyItem.vue
<template>
<li>
<label>
<!-- 添加checked属性,初始化复选框的数据 -->
<input type="checkbox" checked />
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</template>
<script>
export default {
name: 'MyItem',
// 接收MyList组件传递的对象
props: ["todo"]
}
</script>
但是添加checked
后,所有的复选框都被勾选了。
如何让一个标签动态的拥有某一个属性
答:v-bind动态绑定
<input type="checkbox" :checked="todo.done" />
3. 按回车添加todo
输入框在Header组件中,因此Header组件中要添加点击事件
<template>
<div class="todo-header">
<!-- v-model获取表单元素的值 -->
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keydown.enter="add"/>
</div>
</template>
add回调函数中要做以下几件事:
import { nanoid } from 'nanoid'
methods: {
add () {
// 1. 获取到表单元素的内容
console.log(this.title); // 或者通过 e.target.value获取到输入框的内容
// 2. 将用户的输入包装成一个todo对象
const todoObj = { id: nanoid(), title: this.title, done: false }
// 3. 将todo对象添加到todo列表里,即todos数组中
// 4. 输入框内容置空。
this.title = '' // 或者 e.target.value = ''
}
}
-
nanoid
包装的对象应该有一个唯一的id,uuid这个库可以生成全球唯一的编码,但是太大且生成的编码复杂。采用uuid的精简版:nanoid安装:
npm i nanoid
引用:import {nanoid} from 'nanoid'
使用:nanoid()
(这是个函数,直接调用即可) -
todoObj在MyHeader组件里,而数据todos在MyList组件里。两组件为兄弟组件,目前还不会兄弟组件之间通信。所以还是采用props属性。
为了方便MyHeader通信,将todos改放在App组件里,而不是MyList组件里。此时:
App可通过props将todos交给MyList组件进行展示。MyHeader该如何将todoObj交给父组件App呢?
子组件给父组件传值之props
父组件给子组件传递带参的函数
<!--App.vue-->
<template>
...
<!-- 给子组件传函数 -->
<MyHeader :addTodo="addTodo"></MyHeader>
<!-- 给子组件传数据 -->
<MyList :todos="todos"></MyList>
<MyFooter></MyFooter>
...
</template>
<script>
methods: {
// 添加todoObj
addTodo (todoObj) {
this.todos.unshift(todoObj)
}
}
}
</script>
子组件接收函数
<!--MyHeader.vue-->
<script>
props: ["addTodo"],
methods: {
add () {
//完善一下:校验数据
if (!this.title.trim()) return alert('输入不能为空')
const todoObj = { id: nanoid(), title: this.title, done: false }
// 调用函数,通过参数将todoObj传递给父组件
this.addTodo(todoObj)
this.title = ''
}
}
}
</script>
总结:子组件向父组件传值时,父组件需提前向子组件传递一个带参的函数,子组件通过函数的参数将数据传给父组件。
捋顺一下流程:
1、按下回车,执行MyHeader里的add函数,在add里调用了App组件里的addTodo函数。
2、addTodo函数修改了App组件中data里的todos数据。
3、Vue捕获到todos变了,于是重新解析App里的template模板,重新解析时,将变化后的todos交给了MyList组件。
4、MyList收到数据,重新解析自己组件内的模板,v-for,虚拟DOM对比,更新页面。
4. 勾选与取消勾选一个Todo
MyItem.vue
添加change事件(@click也可以)
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" />
handleCheck
事件里需要对todos数据进行修改。数据在App组件里, 数据在哪里,操作数据的方法就在哪里,所以这里需要把待修改的todo的id传递给App。子组件给父组件传值,父组件需要先传递一个带参函数。此处应该是App传给MyList,MyList再传递给MyItem。
App.vue
<template>
...
<!-- 先将函数传递给MyList -->
<MyList :todos="todos" :checkTodo="checkTodo"></MyList>
...
<script>
// 勾选or取消勾选一个todo
checkTodo (id) {
this.todos.forEach((todo) => {
if (todo.id === id) {
todo.done = !todo.done
}
})
}
</script>
MyList.vue
<MyItem v-for="toboObj in todos" :key="toboObj.id" :todo="toboObj" :checkTodo="checkTodo" />
<script>
props: ["todos", "checkTodo"]
</script>
MyItem.vue
// 接收MyList组件传递的对象
props: ["todo", "checkTodo"],
methods: {
handleCheck (id) {
this.checkTodo(id)
}
}
需要注意的是:
:checked="todo.done"
----初始化复选框
@change="handleCheck(todo.id)
----更新复选框
这两个操作可用v-model进行合并
<!-- 这行代码也能实现,但是有点儿违反原则,因为这样修改了props里数据的值,不报错是因为,todo是个对象,地址值没被修改, -->
<input type="checkbox" v-model="todo.done" />
5. 删除
点击按钮,获取当前MyItem的id,根据id删除该条数据。同样的套路
添加监听事件,监听事件里调用父组件传递过来的函数。通过父组件里的这个函数对todos进行删除操作。
App.vue
:
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>
<script>
// 删除一个todo
deleteTodo (id) {
this.todos = this.todos.filter((todo) => {
return todo.id !== id
})
}
</script>
MyList.vue
:
<MyItem
v-for="toboObj in todos"
:key="toboObj.id"
:todo="toboObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
/>
<script>
props: ["todos", "checkTodo", "deleteTodo"]
</script>
MyItem.vue
:
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<script>
// 处理删除
handleDelete (id) {
// 通知App,删除对应的todo
this.deleteTodo(id)
}
</script>
6. footer底部统计
<!--Footer.vue-->
<!--使用计算属性-->
<span>已完成{{ doneTotal }}</span> / 全部{{ total }}
<script>
props: ["todos"],
computed: {
total () {
return this.todos.length
},
doneTotal () {
// reduce记数
return this.todos.reduce((prev, cur) => {
return prev + (cur.done ? 1 : 0)
}, 0)
// 简写
// return this.todos.reduce((prev, current) => prev + (current.done ? 1 : 0))
}
</script>
7. footer底部交互
这里结合6底部统计一起看
7.1 全选框自动打勾
如果代办事项全都选上,则该框自动打勾,
<input type="checkbox" :checked="isAll" @change="checkAll" />
<script>
computed: {
isAll () {
// 已完成的数量是否等于todos的总数
return this.doneTotal === this.total
}
},
</script>
但是这样有个问题:
改进:
isAll () {
return this.doneTotal === this.total && this.total > 0
}
优化:当没有代办事项时,footer组件不应该显示
7.2 全选框取消勾选
方式一:添加点击事件
App组件中创建操作todos的函数并传递给子组件
子组件接收函数并调用
方式二:v-model
<!-- :check = "isAll"用于初始化复选框
@change="checkAll"用来更新复选框
-->
<input type="checkbox" :checked="isAll" @change="checkAll" />
<!-- 简写为 -->
<input type="checkbox" v-model="isAll" />
由于isAll是计算属性,且v-model是双向绑定,所以当操作复选框时,对isAll的值进行了修改;
所以此时isAll不能采用简写形式了
isAll: {
get () {
return this.doneTotal === this.total && this.total > 0
},
set (value) {
this.checkAllTodo(value)
}
}
8. 清除已完成任务
添加点击事件
MyFooter.vue
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
<script>
props: ["todos", "checkAllTodo", "clearAllTodo"],
clearAll () {
this.clearAllTodo()
}
</script>
App.vue
<MyFooter
:todos="todos"
:checkAllTodo="checkAllTodo"
:clearAllTodo="clearAllTodo"
></MyFooter>
<script>
// 清除所有已完成的todo
clearAllTodo () {
this.todos = this.todos.filter((todo) => {
return !todo.done
})
}
</script>
总结
-
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。数据在哪里,对数据进行处理的方法就在哪里
-
props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
-
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
-
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
-
7.2节很好的体现了计算属性set方法的用途
项目完整代码链接:https://gitee.com/LXHST/vue2–Todo-list-case-1.0/tree/master/