• TodoList经典案例


    案例介绍

    如下图所示:
    在这里插入图片描述
    有几个小细节:

    • 当所有事件都被勾选之后,下面已完成前面的框也会自动勾选

    • 只有在至少一个事件被选中之后,才会出现“清除已完成任务”的按钮
      在这里插入图片描述

    • 当列表中没有待办事件的时候,已完成的那一行整体都不会显示
      在这里插入图片描述

    • 添加的事件出现在列表的最上面

    案例要求

    使用前端框架Vue实现
    且不使用全局事件总线、消息订阅与发布的有关知识

    案例分析

    首先根据组件化我们肯定是要对这个待办列表进行拆分的。当然这里有很多拆法,我推荐两种:

    • 分成三个部分:
      List item
    • 分成四个部分
      在这里插入图片描述

    在这里我使用第二种方式,我们将每一个组件命名:

    • 红色部分组件 – headers.vue
    • 绿色部分组件 – tasks.vue
    • 粉色部分组件 – task.vue
    • 蓝色部分组件 – record.vue

    代码实现

    App组件

    <template>
      <div id="app">
        <Headers :addTasks="addTasks" :getLength="getLength"></Headers>
        <Tasks :tasks="tasks" :updateTasks="updateTasks" :deleteTask="deleteTask"> </Tasks>
        <Record :tasks="tasks" :getFinished="getFinished"  :deleteAll="deleteAll" :selectAll="selectAll"></Record>
      </div>
    </template>
    
    <script>
    import Headers from './components/headers'
    import Tasks from './components/tasks'
    import  Record from './components/record'
    
    export default {
      name: 'App',
      components: {
        Headers,
        Tasks,
        Record
      },
      data() {
        return {
          tasks: [
            {id: 1, inform: '睡觉', completed: true},
            {id: 2, inform: '吃饭', completed: false},
            {id: 3, inform: '喝水', completed: true},
            //注意此处是示范数据,在实际时候删除,否则可能会报错。按照代码逻辑,如果你删除一个,再添加一个,就会出现
            //       两个id为3的数据,那么操作会发生混乱。
            //注意:这里的tasks是可以直接进行数据传递的
          ]
        }
      },
      methods: {
        addTasks(task) {
          this.tasks.unshift(task)
          console.log(this.tasks)
        },
        getLength() {
          return this.tasks.length
        },
        updateTasks(id) {
          for (let i = 0; i < this.tasks.length; i++) {
            if (this.tasks[i].id === id) {
              this.tasks[i].completed = !this.tasks[i].completed
            }
          }
        },
        getFinished() {
          let sum = 0
          for (let i = 0; i < this.tasks.length; i++) {
            if (this.tasks[i].completed == true) {
              sum++
            }
          }
          return sum
        },
        deleteTask(id) {
          this.tasks = this.tasks.filter(value => {
            return value.id !== id
          })
        },
    
        deleteAll() {
          this.tasks = this.tasks.filter(value => {
            return value.completed == false
          })
        },
        selectAll(obj) {
          //注意这里要讨论的是按下按钮之后的情况
          if (obj.checked == true) {
            for (let i = 0; i < this.tasks.length; i++) {
              this.tasks[i].completed = true
            }
          } else {
            for (let i = 0; i < this.tasks.length; i++) {
              this.tasks[i].completed = false
            }
          }
        },
    
      }
    }
    
    </script>
    
    <style>
      #app {
        width: 400px;
        text-align: center;
        border-width: 2px;
        border-color: #eaeaea;
        border-style: solid;
        border-radius: 10px;
      }
    </style>
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    注意点:

    • 考虑到有几个组件可能会使用到tasks数据,所以我们把它放到这些组件的父组件App中,如此便于数据的传递
    • tasks里面有几条示范数据。在实际时候删除,否则可能会报错。按照代码逻辑,如果你删除一个,再添加一个,就会出现两个id为3的数据,那么操作会发生混乱。
    • 我们使用props来实现父组件向子组件传递数据的功能
    • 如果子组件要向父组件传递数据,我们首先要在父组件中定义一个方法,传递给子组件,再让子组件调用即可
    • 虽然使用的是data的函数式,但是这里的tasks也是可以直接使用props传递的
    • 展示不需要修改的数据直接props就行;如果要改变数据,数据在哪里方法就在哪里

    headers组件

    <template>
        <input type="text" class="read" placeholder="按下Enter添加待办事件" @keydown.enter="addTask" v-model="task">
    </template>
    
    <script>
    
        export default {
    
            name: "MyHeader",
            props:['addTasks','getLength'],
            data(){
                return {
                    task:'',
                }
            },
    
            methods:{
                addTask(){
                    this.addTasks(
                        {id:this.getLength()+1,inform:this.task,completed:false}
                    )
                    this.task=''
                },
            }
        }
    </script>
    
    <style scoped>
        .read {
            width:370px;
            margin-top: 20px;
            border: 2px solid #e3e2e2;
            border-radius: 10px;
            height: 25px;
            font-size: 20px;
            color: orange;
        }
    
    </style>
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    注意点:

    • 在输入完待办事件,按下enter添加之后,输入框要清空

    tasks组件

    <template>
        <div>
            <ul id="form" v-for="task in tasks" :key="task.id">
                <Task :task="task" :updateTasks="updateTasks" :deleteTask="deleteTask"></Task>
            </ul>
        </div>
    
    </template>
    
    <script>
        import Task from "@/components/task";
        export default {
            name: "MyTasks",
            props:['tasks','updateTasks','deleteTask'],
            components:{
                Task
            }
    
        }
    </script>
    
    <style scoped>
        #form {
            list-style-type: none;
            padding: 0px;
    
        }
    </style>
    
    • 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

    注意:

    • 使用了v-for的标签是不能当作根标签的,所以这里我使用div再包裹一层

    task组件

    <template>
    
        <li class="tasks" >
            <input type="checkbox" class="select" :checked="task.completed" @click="handleTask(task.id)">{{task.inform}}<button class="delete"  @click="deleteOne(task.id)">删除</button>
        </li>
    </template>
    
    <script>
        export default {
            name: "MyTask",
            props:['task','updateTasks','deleteTask'],
            methods:{
                handleTask(id){
                    this.updateTasks(id)
                },
                deleteOne(id){
                    this.deleteTask(id)
                }
    
            },
    
        }
    </script>
    
    <style scoped>
        li {
            text-align: left;
            width: 370px;
            margin: 0 auto;
            border: 2px solid #e3e2e2;
            height: 30px;
            line-height: 30px;
    
        }
        li:hover {
            background-color: #dcdcdc;
            visibility: visible;
        }
    
        li:hover .delete {
            visibility: visible;
        }
        .delete {
            height: 30px;
            color: white;
            background-color: red;
            border-radius: 5px;
            visibility: hidden;
            float: right;
        }
    </style>
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    在做这一部分的时候,难点肯定是怎么作用到具体的元素。怎么理解呢?就是说我们在点击了某一项前面的勾选框之后,我们怎么精确的修改到他的数据?通过id即可,因为我们将点击事件的回调方法放在了App组件中,所以这是一个子组件向父组件传递数据的过程,我们在父组件中定义好方法,并传递给子组件,再由子组件来调用这个方法,在调用的同时传入数据,如此就可以精准的通过id操作到对应的数据。

    record组件

    <template>
        <div class="record" v-show="tasks.length != 0">
    <!--        :checked="finish == tasks.length && tasks.length > 0 "  此处是为了列表删除为空之后,取消勾选-->
            <input type="checkbox"  :checked="getFinished() == tasks.length && tasks.length > 0 " ref="checkAll" @click="selectAll($refs.checkAll)">
            已完成{{getFinished()}}/全部{{tasks.length}}
    <!--        展示这种不需要改变的数据直接props就行,如果要改变数据,数据在哪里方法就在哪里-->
    <!--        v-show里面如果你这个表达式是一个方法,那么这个方法是要带()的-->
            <button class="delete"  @click="deleteAllTasks" v-show="ifShow()">清除已完成任务</button>
        </div>
    </template>
    
    <script>
        export default {
            name: "MyRecord",
    
            props:['getFinished','deleteAll','selectAll','tasks'],
    
            methods:{
                deleteAllTasks(){
                    this.deleteAll()
                    this.$refs.fuyuan.checked = false
                },
                ifShow(){
                    for (let i = 0; i < this.tasks.length; i++) {
                            if(this.tasks[i].completed === true) return true
                    }
    
                    return false
                }
    
    
            }
        }
    </script>
    
    <style scoped>
        .record {
            width:370px;
            margin:0 auto;
            text-align: left;
            height: 35px;
        }
        .delete {
            height: 30px;
            color: white;
            background-color: red;
            border-radius: 5px;
            /*visibility: hidden;*/
            float: right;
        }
    </style>
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    注意:

    • v-show里面如果你这个表达式是一个方法,那么这个方法是要带()的

    在这里插入图片描述
    此处可以不用ref来获取DOM节点,我们也可以使用点击事件对象的target属性来直接获得到这个元素,下面我介绍两种常用的在vue中获取事件对象的方法:
    方法①:$event关键字(适用于事件回调函数有外来参数的时候)
    在这里插入图片描述
    方法②:直接写回调函数名(适用于事件回调函数没有外来参数的时候)
    在这里插入图片描述

    案例总结

    总结TodoList案例

    1. 组件化编码流程:

      ​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

      ​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

      ​ 1).一个组件在用:放在组件自身即可。

      ​ 2). 一些组件在用:放在他们共同的父组件上(状态提升)。

      ​ (3).实现交互:从绑定事件开始。

    2. props适用于:

      ​ (1).父组件 ==> 子组件 通信

      ​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

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

    4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

  • 相关阅读:
    ACM模式各种输入整理(C++)
    网络编程的学习初篇
    JavaScript 日常开发的 9 个实用代码片段 (part 1)
    【人工智能】百度文心一言智能体:AI领域的新里程碑
    如何根据spring定时任务源码写一个自己的动态定时任务组件下
    tensorflow中的常见方法
    Python基础入门篇【22】--python中的函数:初时函数及函数的参数
    直接折半希尔排序
    以太网 TCP协议(三次握手、四次挥手)
    量子+化学材料!微软量子云与美国NobleAI公司合作取得突破
  • 原文地址:https://blog.csdn.net/zyb18507175502/article/details/125531951