赞
踩
也可以按照其他的划分,我是按照功能划分的
创建文件夹,终端进入目录下创建项目
vue create todolist
在组件文件夹下创建四个文件,分别是MyTop.vue;List.vue;Item.vue;MyFooter.vue;
并将四个组件引入App.vue
注意:Top.vue不要写Header.vue,容易和html的标签冲突
后来我的Top.vue也报错了,我改长一点,MyTop,就正常了
注册组件
export default {
name: 'App',
components: { MyTop,MyList,MyFooter}
}
因为item是list的子组件所以item注册在list里面就好啦
<template> <div class="todo-header"> <input type="text" placeholder="请输入任务名称,按回车键确认"> </div> </template> <script > export default { name:'MyTop' } </script> <style scoped> /* header */ .todo-header input{ width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus{ outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82, 168, 236, 0.8); } </style>
<template> <ul class="todo-main"> <MyItem/> <MyItem/> <MyItem/> </ul> </template> <script > import MyItem from './MyItem.vue' export default { name: 'MyList', components: { MyItem } } </script> <style scoped> /* main */ .todo-main{ margin-left: 0; border: 1px solid #ddd; border-radius: 2px; padding: 0; } .todo-empty{ height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
<template> <li> <label> <input type="checkbox"/> <span>xxx</span> </label> <button class="btn btn-danger" style="display: none;">删除</button> </li> </template> <script > export default { name: 'MyItem' } </script> <style > /* item */ li{ list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom:1px solid #ddd; } li label{ float: left; cursor: pointer; } li label li input{ vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button{ float: right; display: none; margin-top: 3px; } li:before{ content: initial; } li:last-child{ border-bottom: none; } </style>
<template> <div class="todo-footer"> <label> <input type="checkbox"> </label> <span> <span>已完成0</span>/全部 </span> <button class="btn btn-danger">清除已完成任务</button> </div> </template> <script > export default { name: 'MyFooter' } </script> <style > /* footer */ .todo-footer{ height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label{ display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input{ position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button{ float: right; margin-top: 5px; } </style>
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyTop/> <MyList/> <MyFooter/> </div> </div> </div> </template> <script> import MyTop from './components/MyTop.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name: 'App', components: { MyTop,MyList,MyFooter} } </script> <style> body{ background: #fff; } .btn{ display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0,0,0,0.2); border-radius: 4px; } .btn-danger{ color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover{ color: #fff; background-color: #bd362f ; } .btn:focus{ outline: none; } .todo-container{ width: 600px; margin: 0 auto; } .todo-container .todo-wrap{ padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
待办事件的数据需要数组来进行存储,id标识,数组保存在List.
在MyList中的MyItem子组件中遍历todos数组,给MyItem传数据。
在MyItem中设置props设置传值
//声明接收todo对象
props:['todo'],
checkbox添加checked就会在页面中显示勾选,动态的拥有某个属性,
:checked="true"
在表达式通过改变true来实现是否勾选
使其=todo.done即可
MyItem.vue
<template> <li> <label> <input type="checkbox" :checked="todo.done"/> <span>{{ todo.title }}</span> </label> <button class="btn btn-danger" style="display: none;">删除</button> </li> </template> <script > export default { name: 'MyItem', //声明接收todo对象 props: ['todo'], mounted() { console.log(this.todo) } } </script>
MyList.vue
<template> <ul class="todo-main"> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"/> </ul> </template> <script > import MyItem from './MyItem.vue' export default { name: 'MyList', components: { MyItem }, data() { return { todos: [ // 数字是有尽头的所以id使用字符串 { id: '001', title: '吃饭', done: true }, { id: '002', title: '睡觉', done: true }, { id: '003', title: '游戏', done: true }, ] } } } </script>
在Top中将用户的输入包装成一个todo对象,将用户的输入todoObj添加到list当中
两个兄弟组件之间传递数据的思路:父组件App中有todos数组(将list中的数据复制到app中),将top中的todoObj传给todos,再通过props将todos中的数据传给list。
<!-- 将addTodo方法传给mytop -->
<MyTop :addTodo="receive"/>
<!-- 将todo数组传给mylist -->
<MyList :todos="todos"/>
props:['addTodo'],
父组件传给子组件一个函数,子组件合适的时候调用。
top传给list数据的整个过程
App中操作todos中的数据unshift可以捕获到数组的修改,todos改变,vue重新解析模板,此时todos增加一个数据,todo数组传给mylist,mylist会重新解析模板,myitem出现四个虚拟dom,dom比较,旧的复用,新的添加。
e.target.value=''
但是这个语句直接操作DOM了,违背了Vue的原则。
input框添加v-model可以避免这个问题
//在methods里面添加
this.title=''
if (!this.title.trim()) return alert('输入不能为空')
勾选与否引起数据的变化,才可以显示完成率,产生交互.
通过勾选找到数据的id,在todo里面找到,然后通知app组件将对应的todo对象的done值取反。
首先需要input框显示勾选的todo(初始化交互)
:checked="todo.done"
绑定事件响应是否勾选(click/change都可以)
@click="handleCheck(todo.id)
可以通过点击输出勾选事件的id
console.log(id)
app组件中,通知app组件将对应的todo对象的done值取反
checkTodo(id) {
// 遍历list中的item
this.todos.forEach((todo) => {
if (todo.id === id) todo.done=!todo.done
})
}
组件间通信
本次采用逐次传递。
在App中将checkTodo传递给MyList组件,在MyList组件中使用props接收checkTodo
<MyList :todos="todos" :checkTodo="checkTodo"/>
props:['todos','checkTodo'],
因为是Myitem使用,所以在MyList组件中的MyItem中单项数据绑定
:checkTodo="checkTodo"
在MyItem中使用props接收checkTodo。
MyItem中input框使用v-model双向数据绑定v-model=“todo.done”,通过布尔值决定input的checkbox是否勾选
props传值todo,todo的属性值修改,vue监测的就是todo属性
但是props的值不允许修改
看一下下面的例子
let a=3;
a=8;
修改了
let obj={a:2,b:3}
obj.=666
obj={x:100,y:200}
vue只能监测到obj={x:100,y:200}。
在本项目中myItem中添加
<input type="checkbox" v-model="a"/>
props接收a
但是这种方法并不推荐,因为有点违反原则,修改了props,vue监测不到。
在item中给按钮button添加点击事件handleDelete,注意接受todo的id
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
handleDelete(id) {
// 删除
if (confirm('确定删除吗?')) {
// console.log(id)
}
}
deleteTodo(id) {
this.todos = this.todos.filter((todo) => {
return todo.id!==id
})
}
再次经历爷孙传值
先App传给MyList,MyList中props接收deleteTodo,MyItem标签中单项数据绑定deleteTodo。之后MyList传给MyItem,item的props接收即可。
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
props: ['todo', 'checkTodo','deleteTodo'],
handleDelete(id) {
// 删除
if (confirm('确定删除吗?')) {
// console.log(id)
this.deleteTodo(id)
}
}
首先将todos传给myitem
//App.vue
<MyFooter :todos="todos"/>
//MyFooter.vue
props:['todos']
使用计算属性computed实现底部统计
<span>已完成{{doneTotal}}</span>/全部{{ todos.length }}
computed: {
doneTotal() {
let i = 0
this.todos.forEach((todo) => {
if(todo.done)i++
});
return i
}
}
底部统计也可以使用ES6的reduce方法完成一种更为高级的写法
const x= this.todos.reduce((pre,cur) => {
console.log('@', pre, cur)
return pre + (cur.done?1:0)
}, 0)
console.log('###', x)
return x
精简为一句代码
return this.todos.reduce((pre,cur)=>pre+(cur.done?1:0),0)
pre上一个值,cur当前的值,数组的长度就是函数的调用次数.
打印pre,0,undefined,undefined。统计的初始值是0,所以第一次调用是0,再调用的时候,pre是第一次调回的函数的返回值,第一次调用没有返回值,所以第二次和第三次都是undefined。如果有return 3,那么第二次和第三次pre都是3.所以返回值应该是return pre+1.这样第一次返回值是1,第二次是2,第三次是3。所以最后3就是reduce的返回值。
全部{{ total}}
computed: {
total() {
return this.todos.length
},
}
当任务为0时,不显示底部
<MyFooter :todos="todos" :checkAllTodo="checkAllTodo"/>
checkAllTodo(done) {
this.todos.forEach((todo) => {
todo.done=done
})
}
Myfooter.vue中props接收传值,然后方法调用
props: ['todos','checkAllTodo'],
methods: {
checkAll(e) {
console.log(e.target.checked)//借助属性拿到Dom没有操作DOM
this.checkAllTodo(e.target.checked)
}
}
v-model替换
点击勾选框,[Vue warn]: Write operation failed: computed property “isAll” is readonly.
将isAll改为计算属性
isAll(){}改成isAll: {}
思路:给清空已完成的按钮设置click事件clearAll
methods设置方法clearAll,clearAll调用App入口文件的clearAllTodo方法,给myfooterclearAllTodo传给MyFooter组件
clearAllTodo() {
this.todos=this.todos.filter((todo) => {
return !todo.done
})
}
MyFooter文件使用props接收文件。
## 清空已完成任务
> 思路:给清空已完成的按钮设置click事件clearAll
methods设置方法clearAll,clearAll调用App入口文件的clearAllTodo方法,给myfooterclearAllTodo传给MyFooter组件
clearAllTodo() {
this.todos=this.todos.filter((todo) => {
return !todo.done
})
}
MyFooter文件使用props接收文件。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。