Vue2学习day05—Todo-list案例
本篇博客为跟着尚硅谷Vue课程做的Todo-list案例,目的是了解熟悉组件化编码流程 开始之前先对组件化编码流程做下说明:
- 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
- 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。 2). 一些组件在用:放在他们共同的父组件上(状态提升)。 - 实现交互:从绑定事件开始。
拆分静态组件
首先我们来完成静态组件先来分析下页面 经过分析我们可建立如下项目组件结构:
静态页面实现
各组件页面静态代码如下
-
TodoHeader.vue <template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
</template>
<script>
export default {
name:'TodoHeader',
}
</script>
<style scoped>
.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.6);
}
</style>
-
TodoItem.vue <template>
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</template>
<script>
export default {
name:'TodoItem',
}
</script>
<style scoped>
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>
-
TodoList.vue <template>
<ul class="todo-main">
<TodoItem />
</ul>
</template>
<script>
import TodoItem from './TodoItem'
export default {
name:'TodoList',
components:{
TodoItem
}
}
</script>
<style scoped>
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
</style>
-
TodoFooter.vue <template>
<div class="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</template>
<script>
export default {
name:'TodoFooter',
}
</script>
<style scoped>
.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>
-
App.vue <template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<TodoHeader />
<TodoList />
<TodoFooter />
</div>
</div>
</div>
</template>
<script>
import TodoHeader from './components/TodoHeader'
import TodoFooter from './components/TodoFooter'
import TodoList from './components/TodoList'
export default {
name:'App',
components:{
TodoHeader,
TodoFooter,
TodoList
}
}
</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.05);
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>
实现动态组件
初始化列表
一堆数据使用数组存储,数据项如果较为复杂,数组中的数据项使用对象实现 首先动态的拿到列表中的数据进行展示
- 在
TodoList.vue 中添加数据项,并对TodoItem使用v-for遍历,并将数据传递到TodoItem data() {
return {
todos:[
{id:'001',title:"敲代码",done:false},
{id:'002',title:"吃早饭",done:true},
{id:'003',title:"出门",done:false}
]
}
},
<TodoItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"/>
- 在TodoItem中接收数据,并显示到页面
props:['todo']
<label>
<input type="checkbox" :checked="todo.done" />
<span>{{todo.title}}</span>
</label>
实现交互
添加
在TodoHeader给输入框添加数据绑定并绑定一个键盘事件v-model="title" @keyup.enter="add" 在add方法中我们将用户的输入绑定成一个todo对象,并将其传递给TodoList 我们可以使用uuid为我们生成全球唯一的标识符,但由于uuid包比较大,这里我们使用简化的nanoid
npm install nanoid
nanoid使用的是分别暴露,所以我们使用如下方式引入
import {nanoid} from 'nanoid'
将用户的输入包装成一个todo对象
const todoObj = {id:nanoid(),title:this.title,done:false}
data(){
return{
title:''
}
},
methods:{
add(e){
if(!this.title.trim()) return alert('输入不能为空')
const todoObj = {id:nanoid(),title:this.title,done:false}
console.log(todoObj)
this.title=''
}
}
如何将TodoHeader中的数据传递给到TodoList中呢? 二者的关系为兄弟,兄弟之前传递数据以我们目前所学的知识是办不到的,我们之前学的一直是父亲给给孩子传递数据,我们先用最初级的方式来解决下这个问题,既然两个兄弟之间无法传递数据,那我们可以将数据给他们共同的父亲来间接的传递,如下图: 孩子给父亲传递数据,需要父亲提前给孩子一个函数,孩子在自己里面调用这个函数,而函数的定义是在父亲中的,故父亲即可收到参数 将我们之前在TodoList组件中定义的todos数据拿到App组件中
data() {
return {
todos:[
{id:'001',title:"敲代码",done:false},
{id:'002',title:"吃早饭",done:true},
{id:'003',title:"出门",done:false}
]
}
},
拿过来后我们需要将App组件中的todos传递给TodoList组件
<TodoList :todos="todos"/>
在TodoList组件中接收
props:['todos']
编写addTodo函数,以能接收到其孩子TodoHeader传递的数据
methods:{
addTodo(todoObj){
this.todos.unshift(todoObj)
}
}
<TodoHeader :addTodo="addTodo" />
TodoHeader组件调用addTodo函数 先接收
props:['addTodo'],
之后在add方法中清空输入前去通知App组件去添加一个todo对象
this.addTodo(todoObj)
勾选
当我们勾选每个列表项的check框时需要更改todos中的数据项的done属性,对其进行取反操作,这里的数据传递是孙子->爷爷,以目前所学,我们可以借助其父亲做个过渡 两对父子还是采用函数的形式,即父亲提前给孩子一个函数,孩子在自己里面调用这个函数。逻辑在添加操作已进行分析,这里不再缀述。
- ‘爷爷’:App组件
- 勾选处理函数
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id === id) todo.done = !todo.done
})
},
- 传递给其孩子TodoList组件
<TodoList :todos="todos" :checkTodo="checkTodo" />
- ‘父亲’:TodoList组件
- 接收其父亲App组件传递的checkTodo
props:['todos','checkTodo']
- 传递给其孩子TodoItem组件
<TodoItem v-for="todoObj in todos" :key="todoObj.id"
:todo="todoObj" :checkTodo="checkTodo" />
- ‘孙子’:TodoItem组件
- 接收其父亲TodoList组件传递的checkTodo函数
props:['todo','checkTodo'],
- 给checkbox框绑定事件
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" />
- 添加响应处理方法
handleCheck(id){
this.checkTodo(id)
},
删除
现在我们来看删除逻辑,其实很简单,和勾选类似,也是孙子给爷爷传递数据,组件间的函数传递接收这里不再缀述,仅把一些关键的逻辑写一下
- App组件中的删除todo方法
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !== id
})
},
- TodoItem组件调用
handleDelete(id){
if(confirm('确定删除吗')){
this.deleteTodo(id)
}
}
全选or取消全选
- ‘父亲’:App组件
- 定义checkAllTodo函数
checkAllTodo(done){
this.todos.forEach((todo)=>{
todo.done = done
})
},
- 将checkAllTodo函数传递给其孩子TodoFooter组件
<TodoFooter :todos="todos" :checkAllTodo="checkAllTodo" />
- ‘孩子’:TodoFooter组件
- 接收其父亲传递的函数
props:['todos','checkAllTodo'],
- 给底部checkbox绑定点击事件
<input type="checkbox" v-model="isAll"/>
- 显示统计信息
<span>已完成{{doneTotal}}</span> / 全部{{total}}
- 计算属性doneToal,total,isAll
computed:{
total(){
return this.todos.length
},
doneTotal(){
return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
},
isAll:{
get(){
return this.doneTotal === this.total && this.total > 0
},
set(value){
this.checkAllTodo(value)
}
}
},
清除所有已完成的todo
经过上面的学习,大家肯定已经掌握了数据传递方法,这里只写关键逻辑
效果图
注意点
- props适用于:
(1).父组件 ==> 子组件 通信 (2).子组件 ==> 父组件 通信(要求父先给子一个函数) - 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
- props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
|