当前位置:   article > 正文

Vue实战——TodoList案例(附js原生和vue源码)_todolist源码

todolist源码

TodoListVue实战案例(1.0)

组件化编程流程(通用)

  1. 拆分静态组件:按照功能抽取组件,使用组件实现静态页面效果
    注意:命名不要和html标签冲突

  2. 实现动态组件( 展示动态数据)
    注意:所以data、props、methods、computed的数据命名不要重名
    2.1 数据的类型、名称是什么?
    数据的名称:Todos
    数据的类型:对象数组
    2.2 数据保存在哪个组件?
    考虑好数据的存放数据,数据是一个组件在用还是一些组件在用
    1. 一个组件在用:放在组件自身即可
    2. 多个组件在用:放在他们共同的父组件上

  3. 实现交互——从绑定时间监听开始

1. 实现静态网页

将项目分为多个组件,分析其关系。
将之前用原生js做的TodoList项目ctrl+cctrl+v到相应的组件。基本实现静态组件。
效果
在这里插入图片描述

2. 展示动态数据,实现交互

2.1初始化列表

定义数据

一堆数据(要做的事情) -> 数组
每一个数据(要做的事) -> 对象(具有不同的属性)

todos:[
             {id:'001',title:"xxx",done:true},
             {id:'002',title:"xxx",done:false},
             ...,
           ]
         }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
展示数据
  1. <UserItemtodo v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" />
    根据数组的数组的数量去遍历用多少次UserItemtodo组件,v-for:"(item,index) in todos",
    把数组中每一个具体信息的对象传递UserItemtodo组件:DemoName:"item"

  2. UserItemtodo组件中声明接收传递过来的数据(todo对象)
    props:['todo']

  3. 根据每个对象的信息按需求渲染页面
    根据对象信息(done属性)来控制复选框是否勾选(todo.done为true则选中,todo.done为false则不选中)<input type="checkbox" :checked="todo.done"/>
    展示(每个要做的事情的名字): <span>{{todo.title}}</span>

2.1添加功能

添加一个todo

  1. 给输入框绑定一个键盘事件input type="text" placeholder="回车添加" @checked.enter="add">
  2. 获取用户数据
    方法一:
add(event){ //event 发生事件的元素
           console.log(e.target.value)
      	 },
  • 1
  • 2
  • 3

方法二:
双向数据绑定 <input type="text" placeholder="回车添加" @keyup.enter="add" v-model="title"> </div>
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的

  data(){
          return{
              title:''
          }
      },
      methods:{
          add(e){
             console.log(this.title}
          },
      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

我这里使用第一种方法。

  1. 将用户的输入包装成一个对象
add(e){
// 将获取到的名字包装成一个todo对象
	const todoObj={id:''}
}
  • 1
  • 2
  • 3
  • 4

这里的id没有服务器和数据的支撑,需要生成一个独一无二的id作为辨识。
那如何生成不同的id呢?我们可以使用一个库nanoid
安装这个库npm i nanoid

//使用这个库
import {nanoid} from 'nanoid'//直接调用nanoid函数,它就能给你一个唯一的字符串
export default {
        name:'UserHeader',
        methods:{
            add(e){
                const todoObj={id:nanoid(),title:e.target.value,done:false}
            },
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.将打包好的todo对象加入到todos数组中
回归到本质问题——数据保存在哪个组件?
如果todos数组保存在和MyHeader同级的MyList组件中,同级组件之间难以传输数据。
props适用于:
父组件 == 子组件 通信
子组件==> 父组件 通信(要求父组件先给子组件一个函数)
因此todos数组应该保存在App组件中
在这里插入图片描述
App.vue
this.todos.unshift(todoObj)Vue捕获到的你对todos的修改,开始重新解析模板

<template>
  <div id="root">
    <UserHeader :Addtodo="Addtodo"/>//将Addtodo函数传给UserHeader组件
    <UserList :todos="todos"/>//将todos数组传给UserList组件
    <UserFooter/>
</div>
</template>

<script>
import UserHeader from './components/UserHeader'
import UserList from './components/UserList'//List组件包括Itemtodo组件和Itemdone组件
import UserFooter from './components/UserFooter'
export default {
  name: 'App',
  components: {
    UserHeader,UserList,UserFooter
  },
  data(){
    return{
      todos:[]
          }
  },
  methods:{
    Addtodo(todoObj){
    //为什么不叫add()?  与UserHeader中的methods中的add函数重名了
    //将接收到的todo对象加入到数组最前边
      this.todos.unshift(todoObj)
    }
  }
}
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

UserList组件

<template>
       <div class="container">
        <div class="left">
            <h3>To do</h3>
            <UserItemtodo v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" />
       </div>
        <div class="right">
            <h3>Done</h3>
            <UserItemdone v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"/>
        </div>
    </div>
</template>

<script>
    import UserItemtodo from './UserItemtodo'
    import UserItemdone from './UserItemdone'
    export default {
        name:'UserList',
        components:{
            UserItemtodo,
            UserItemdone
        },
        props:['todos']
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

UserHeader组件
UserHeader组件里调用Vue组件传递过来的函数,而函数的定义本身在App组件中,App就能接收到参数

<template>
   <div class="nav">
	<input type="text" placeholder="要添加什么任务呢?(回车添加)"  @keyup.enter="add" >
    </div>
</template>
<script>
    import {nanoid} from 'nanoid'
    export default {
        name:'UserHeader',
        props:['Addtodo'],
        methods:{
            add(){
             	//校验数据
                if(!e.target.value.trim()) return alert("输入不能为空")
                const todoObj={id:nanoid(),title:e.target.value,done:false}
                //通知App组件去添加一个todo对象
                this.Addtodo(todoObj)
              	//清空输入
                e.target.value=''//回车添加一个事件后,清空输入框
            },
        }
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

2.2 勾选功能

  1. 初始化显示(哪些事件勾选,哪些事件不勾选)
    <input type="checkbox" :checked="todo.done" />
  2. 产生交互
    2.1 根据点击按钮改变todo对象的布尔值
    方法一:
    根据事件的点击/改变调用函数
    根据事件调用函数
<input type="checkbox" :checked="todo.done" @click="handlecheck(todo.id)"/>
or
<input type="checkbox" :checked="todo.done" @change="handlecheck(todo.id)"/>
  • 1
  • 2
  • 3

函数
数据在哪里,操作数据的方法就在哪里
App组件中写函数

  methods:{
    Addtodo(todoObj){
      //添加一个todo
      this.todos.unshift(todoObj)
    },
     //勾选or取消勾选一个todo
     checktodo(id){
      this.todos.forEach((todo)=>{//遍历todos数组中的每一个todo对象
        //函数体
        if(todo.id==id) todo.done=!todo.done
      })
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

App组件将函数传给UserList组件,UserList组件再把函数传给UserItemtodo组件
(组件间通信的方法暂时只用此种)
UserIntemtodo组件

props:['todo','checktodo'],
        methods:{
            handleCheck(id){
                //通知App组件将对应的todo对象的done值取反
                this.checktodo(id)
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

方法二:
input框是checkbox类型,并且v-model绑定的是一个布尔值,那么这个v-model就能够实现两个事:
1.初始化列表 维护好是否勾选
2.双向数据绑定 勾选与取消勾选会引起todo.done的改变

 <!-- 如下代码也能实现代码,但是不太推荐,因为有点违反原则,因为修改了props -->
<input type="checkbox" v-model="todo.done"/>
  • 1
  • 2

注意:Vue只是很浅层的监视props传递的数据是否修改

  • 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的

  • props传过来的若是对象类型的值(地址没变),修改对象中的属性时Vue不会报错,但不推荐这样做

    2.2 根据todo对象的done属性值判断在对应位置是否显示(通过v-forv-if实现)

UserItemtodo组件

<div class="Item" v-show="!todo.done">
        <label>
         <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
         <span >{{todo.title}}</span>
        </label>
         <button class="btn" @click="handleDelete(todo.id)">删除</button>
    </div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

UserItemdone组件

    <div class="Item" v-show="todo.done">
        <label>
         <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
         <span>{{todo.title}}</span>
        </label>
         <button class="btn" @click="handleDelete(todo.id)">删除</button>
    </div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

效果:
在这里插入图片描述

2.3 删除功能

1.鼠标悬浮有高亮效果
css简单实现

.Item:hover{
    background:rgba(0,0,0,0.30);
}
  • 1
  • 2
  • 3

2.点击删除按钮获取事件id

        methods:{
            //勾选or取消勾选
            handleCheck(id){
                //通知App组件将对应的todo对象的done值取反
                console.log(id)
                this.checktodo(id)
            },
            //删除
            handleDelete(id){
                if(confirm('确定删除吗')){
                    console.log(id);
                }
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.在App组件中删除id对应的todo对象

App组件中定义函数:

    //删除一个todo
    deletetodo(id){
     	this.todos=this.todos.filter(todo=>todo.id!==id)
      //过滤掉不想要的,得到新数组赋值回原数组
    }
  • 1
  • 2
  • 3
  • 4
  • 5

App组件将函数传给UserList组件,UserList组件再把函数传给UserItemtodo组件
(组件间通信的方法暂时只用此种,其他方法以后再讲)
UserItemtodo组件

//删除
            handleDelete(id){
                if(confirm('确定删除吗')){
                    this.deletetodo(id)
                }
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.4 底部统计功能

1.UserFooter组件获取todos数组

//App中
  <UserFooter :todos="todos"/>``
 //UserFooter中
  props:['todos']``
  • 1
  • 2
  • 3
  • 4

2.数组数据按照需求渲染到页面
UserFooter组件

<span class="done">已完成{{doneTotal}}</span>
<span class="all">全部{{todos.length}}</span>`
  • 1
  • 2

方法一:

computed:{
            doneTotal(){
                let i=0;
                this.todos.forEach((todo) => {
                    if(todo.done) i++;
                });
                return i;
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

方法二:
使用reduce函数

const x=this.todos.reduce((pre,cur)=>{//函数调用次数=数组长度
                //console.log(pre,cur);//cur 每一个todo项
                return pre+(cur.done?1:0)
              },
              0)//0为pre的初始值
              return x;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

精简代码

        computed:{
            doneTotal(){
              return this.todos.reduce((pre,todo)=>pre+(todo.done ?1 : 0),0)
            }
        }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

todos数组长度就是全部的数量
todos数组中todo.done=true的数量就是已完成的数量

2.5底部交互功能

  1. 全选功能

1.1 优化事件全部数量的显示方法(全部事件等于0时不显示)

<div class="footer" v-show="total">
<input type="checkbox" :checked="doneTotal==total"/>全选完成
  • 1
  • 2
total(){
         return this.todos.length
       },
  • 1
  • 2
  • 3

1.2 如果tododone值为真,全选复选框勾选(且事件个数大于0)

<input type="checkbox" :checked="isAll"/>全选完成
  • 1
isAll(){
           return this.doneTotal===this.total && this.total>0
 }
  • 1
  • 2
  • 3

1.3 点击全选复选框,将所有tododone属性设为true
App组件
定义一个全选函数

 //全选or取消全选
    checkAlltodo(done){
      this.todos.forEach((todo)=>{
        todo.done=done
      })
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

方法一:
UserFooter组件
获取复选框是否勾选,接收App传递的函数,调用函数

<input type="checkbox" :checked="isAll" @change="checkAll"/>全选完成
  • 1
isAll(){
      return this.doneTotal===this.total && this.total>0
}
checkAll(e){
           this.checkAlltodo(e.target.checked);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

方法二:
:checked="isAll" @change="checkAll"初始化展示+交互->v-model
`UserFooter``

<input type="checkbox" v-model="isAll"/>全选完成
  • 1

出现错误
在这里插入图片描述
isAll函数使用简写方式时,isAll只能被定义而不被修改
UserFooter组件

 <input type="checkbox" v-model="isAll"/>全选完成
  • 1
isAll:{//自己计算出来的,不是props传递过来的数据
                get(){//定义获取
                    return this.doneTotal===this.total && this.total>0
                },
                set(value){//修改
                    this.checkAlltodo(value);
                }
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 删除全部已完成事件功能

App组件

//清除所有已经完成的todo
    clearAlltodo(){
      this.todos=this.todos.filter(todo=>!todo.done)
    }
  • 1
  • 2
  • 3
  • 4

App组件将clearAlltodo函数传递给UserFooter组件

UserFooter组件
todos数组中的todo.done=true的`对象全部删除

<button class="btn btn-danger" @click="claerAll">清除已完成任务</button>
  • 1
            claerAll(){
                this.clearAlltodo();
            }
  • 1
  • 2
  • 3

最终效果

在这里插入图片描述

项目源代码

Todolist

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/223895
推荐阅读
相关标签
  

闽ICP备14008679号