TodoList案例
添加
在输入框输入,anenter键添加到MyList组件中
MyHeader.vue:
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add" v-model="content">
<!-- 1.@keyup.enter="add"绑定一个键盘事件,按enter键添加一个元素,事件名字add -->
<!-- 2.v-model="content",这里是为了下面获得表单元素,先把content定义为空。也可以用另一种方法:add(event){console.log(event.target.value) -->
</div>
</template>
<script>
import {nanoid} from "nanoid" //nanoid引入使用分别引入
// 引入nanoid,nanoid是用来生成唯一id的小型库
export default {
name:"MyHeader",
data(){
return {
content:""
}
},
methods:{
add(){
if(!this.content.trim()){ //空字符串布尔值是false,取反则为true;trim()是去掉前面的空格,这样写更完整。
return alert("输入不能为空")
}
// step1:获取表单元素
// console.log(this.content)
// step2:由于todoobj是由id,title,done组成的,所以要将用户输入的内容包装成todoobj
const todoobj={id:nanoid(),title:this.content,done:false}
// id:nanoid()的使用方法
// step3:实现将表单中输入的内容添加到列表中。
// 这时要将MyList和MyHeader两个组件建立联系,则需要用到他们共同的父亲App组件。
// todos是共用的,将其放在App组件
// 将在MyList组件中的todos数据给父亲App,然后App传送数据给MyList组件;(用props,父传子)
// MyHeader里的todoobj给App,(子传父),父亲先给儿子一个函数,儿子调用这个函数。
this.addTodoObj(todoobj) //调用
//step4:细节进行修改
// 当添加完元素之后,输入框为空
this.content=""
// 写判断语句判断输入框是否为空,空的话按enter键无效(要写在上方才可以)
}
},
props:["addTodoObj"] //接收数据
}
</script>
MyItem.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done">
<!--1. :checked="todo.done"根据done是true还是false确定是否默认勾选 -->
<span>{{todo.title}}</span>
<!-- 2.插值语法,对应需要的值 -->
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</template>
<script>
export default {
name:"MyItem",
// 1.接收来自MyList组件传来的信息
props:["todo"]
}
</script>
MyList.vue
<template>
<ul class="todo.main">
<!-- 遍历todos数组,要用多少次MyItem;并且要将每一条todoobj对应属性传给MyItem(用props) -->
<MyItem v-for="todoobj in todos" :key="todoobj.id" :todo="todoobj"/>
</ul>
</template>
<script>
import MyItem from './MyItem' //注意路径和前边的不一样
export default {
name:"MyList",
components:{
MyItem
},
props:["todos"]
}
</script>
App.vue
<template>
<div class="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodoObj="addTodoObj"/> <!-- 将addTodoObj传给MyHeader组件 -->
<MyList :todos="todos"/> <!-- 将todos传给MyList组件 -->
<MyFooter/>
</div>
</div>
</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:[
{id:"001",title:"打代码",done:"true"}, //id一般用字符串,done表示完成
{id:"002",title:"睡觉",done:"false"},
{id:"003",title:"吃饭",done:"true"}
]
}
},
methods:{
addTodoObj(todoobj){
this.todos.unshift(todoobj)
}
},
components: {
MyHeader,
MyList,
MyFooter
}
}
</script>
勾选
MyList.vue
<template>
<ul class="todo.main">
<!-- 遍历todos数组,要用多少次MyItem;并且要将每一条todoobj对应属性传给MyItem(用props) -->
<MyItem v-for="todoobj in todos" :key="todoobj.id" :todo="todoobj" :checkTodo="checkTodo"/>
</ul>
</template>
<script>
import MyItem from './MyItem' //注意路径和前边的不一样
export default {
name:"MyList",
components:{
MyItem
},
props:["todos","checkTodo"]
}
</script>
MyItem.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @click="handleCheck">
<!--1. :checked="todo.done"根据done是true还是false确定是否默认勾选 -->
<!-- @click="handleCheck"注册点击事件确认是否勾选 -->
<!-- <input type="checkbox" :checked="todo.done" v-model="todo.done">
这样写可以直接实现,但是不推荐,因为props里的数据最好不要修改,这样的话虽然不会报错,但是修改了props里todo的数据。 -->
<span>{{todo.title}}</span>
<!-- 2.插值语法,对应需要的值 -->
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</template>
<script>
export default {
name:"MyItem",
// 1.接收来自MyList组件传来的信息
props:["todo","checkTodo"],
methods:{
handleCheck(){
// 找到勾选项对应的id
// 在todos找到对应的id
// 给对应id的done值取反
// 调用App中的函数
this.checkTodo()
}
}
}
</script>
App.vue
<template>
<div class="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodoObj="addTodoObj"/> <!-- 将addTodoObj传给MyHeader组件 -->
<MyList :todos="todos" :checkTodo="checkTodo"/> <!-- 将todos传给MyList组件 -->
<MyFooter/>
</div>
</div>
</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:[
{id:"001",title:"打代码",done:"true"}, //id一般用字符串,done表示完成
{id:"002",title:"睡觉",done:"false"},
{id:"003",title:"吃饭",done:"true"}
]
}
},
methods:{
addTodoObj(todoobj){
this.todos.unshift(todoobj)
},
checkTodo(id){
this.todos.forEach((todoobj)=>{
if(todoobj.id===id) {
todoobj.done!==todoobj.done
}
})
}
},
components: {
MyHeader,
MyList,
MyFooter
}
}
</script>
删除
MyItem.vue:
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
handleDelete(id){
if (confirm("确定删除吗")) {
this.deleteTodo(id)
}
}
App.vue
// 删除
deleteTodo(id){
this.todos.forEach((todoobj,index) =>{
if(todoobj.id===id){
this.todos.splice(index,1,id)
}
})
}
// 或者:
// deleteTodo(id){
// this.todos=this.todos.filter(todo=>todo.id!==id
// })
},
底部计算
MyFooter.vue
<span>
<span>已完成{{doneTotal}}</span>/全部{{todos.length}}
</span>
props:["todos"],
computed:{
doneTotal(){
return this.todos.reduce((pre,current)=>pre+(current.done?1:0),0)
}
}
}
底部交互
App.vue
// 清除所有已经完成的todo
clearAllTodo(){
this.todos=this.todos.filter((todo)=>!todo.done)
}
Myfooter.vue
computed:{
doneTotal(){
return this.todos.reduce((pre,current)=>pre+(current.done?1:0),0)
},
total(){
return this.todos.length
},
isAll(){
//满足下面两个条件直接返回布尔值
return this.doneTotal===this.todos.length && this.total>0
}
// 或者:整合在一起
// isAll:{
// get(){
// return this.doneTotal===this.todos.length && this.total>0
// },
// set(value){
// checkAll(e){
// this.checkAllTodo(e.target.checked)
// }
// }
// }
},
methods:{
checkAll(e){
this.checkAllTodo(e.target.checked) //全选或全不选
},
// 清除所有已经完成的todo
clearAll(){
this.clearAllTodo()
}
},
}
</script>
总结TodoList案例
1.组件化编码流程
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件再用,还是一些组件在用。
(1)一个组件在用:放在组件自身即可
(2)一些组件在用:放在他们共同组件上(状态提升)
(3)实现交互:从绑定事件开始
2.props适用于:
(1)父组件==>子组件 通信
(2)子组件==>父组件 通信(要求父亲先给儿子一个函数,儿子自己调用这个函数)
3.使用v-model要切记:
v-model绑定的值不能是props传过来的值,因为props是不可以修改的!!