• Vue项目实战——实现一个任务清单(学以致用,两小时带你巩固和强化Vue知识点)


    Vue2.x 项目实战(一)

    内容参考链接
    Vue2.x全家桶Vue2.x 全家桶参考链接
    Vue2.x项目(一)Vue2.x 实现一个任务清单
    Vue2.x项目(二)Vue2.x 实现GitHub搜索案例
    Vue3.x项目(三)Vue3.x 实现一个任务清单


    Vue2.x 实现 todoList

    1、前言

    如果你对 vue 的基础知识还很陌生,推荐先去学习一下 vue 基础

    • 如果你 刚学完 vue 基础知识,想检查一下自己的学习成果
    • 如果你 已学完 vue 基础知识,想快速回顾复习
    • 如果你 已精通 vue 基础知识,想做个小案例
    • 那不妨看完这篇文章,我保证你一定会有收获的!

    2、项目演示(一睹为快)

    todoList 项目演示

    在这里插入图片描述

    3、涉及知识点

    • Vue基础:插值语法,常用指令,键盘事件,列表渲染,计算属性,事件监听,生命周期
    • Vue进阶:props(父传子),自定义事件(任意组件间通信),自定义事件的解绑,$nextTick 异步
    • 本地存储:任务记录保留在当前浏览器中,长期有效(不手动销毁则一直保留)
    • 第三方库:nonoid(下载导入即可使用)

    备注:

    1. 任意组件间的通信方式有很多种(全局事件总线,消息订阅预发布…),熟练掌握一种即可(推荐自定义事件,配置简单,容易理解)
    2. 本文是 vue 基础的练习项目,不涉及 vue 周边(Vuex,Vue-router)

    4、项目详情(附源码及解析)

    该项目有 五个组件 构成:

    (1)App.vue 父组件,以上四个子组件 最终归并的地方,并实现很多功能相关方法

    (2)MyHeader.vue 子组件:头部,用于用户文本框 输入添加任务事项

    (3)MyList.vue 子组件:躯干,用于 呈现任务的列表

    (4)MyItem.vue 子中子组件,Mylist.vue 的子组件,用于 呈现每个任务及编辑删除

    (5)MyFooter 子组件,用于 显示所选个数和总个数及删除已完成任务

    App.vue 父组件

    • 所有子组件的汇集点
    • 里面定义里很多方法,通过 props 父传子,供子组件们去使用
    • 当然也有自定义事件,供子给父传值,进行页面的渲染更新
    <template>
      <!-- 最外层容器 -->
      <div class="todo-container">
        <div class="todo-wrap">
          <!-- 头部子组件,子传父,自定义 addTodo事件,添加一个 todo对象 -->
          <MyHeader @addTodo="addTodo" />
          <!-- 任务列表子组件,父传子,动态绑定对应事件 -->
          <MyList :updateTodo="updateTodo" :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo" />
          <!-- 底部子组件,子传父,全选和全清除 -->
          <MyFooter
            :todos="todos"
            @checkAllTodo="checkAllTodo"
            @clearAllTodo="clearAllTodo"
          />
        </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",
      components: { MyHeader, MyList, MyFooter },
      data() {
        return {
          // 由于 todos 是 MyHeader 组件 和 MyFooter 组件都在用,所以放在APP中(状态提升)
          // 解析 JSON字符串 第一次使用时 null 身上没有 length 属性会报错,所以添加||,前面不能用时,置为空数组
          // localStorage.getItem("xxx") 用于从本地存储中读取 todos
          todos: JSON.parse(localStorage.getItem("todos")) || [],
        };
      },
      methods: {
        // 添加一个 todo
        addTodo(todoObj) {
          this.todos.unshift(todoObj);
        },
        // 勾选 or 取消勾选一个todo
        checkTodo(id) {
          this.todos.forEach((todo) => {
            if (todo.id === id) todo.done = !todo.done;
          });
        },
        // 更新一个 todo
        updateTodo(id, title) {
          this.todos.forEach((todo) => {
            if (todo.id === id) todo.title = title;
          });
          
        },
        // 删除,todo.id !== id 就不会 push 该 todo,即删除
        deleteTodo(id) {
          this.todos = this.todos.filter((todo) => todo.id !== id);
        },
        // 全选 or 取消全选
        checkAllTodo(done) {
          this.todos.forEach((todo) => {
            todo.done = done;
          });
        },
        // 清除所有已经完成的todo
        clearAllTodo() {
          this.todos = this.todos.filter((todo) => {
            return !todo.done;
          });
        },
      },
      watch: {
        todos: {
          // 深度监视 检测到是否被勾选
          deep: true,
          handler(value) {
            // localStorage.setItem("xxx") 用来添加 todo
            // 格式化为 JSON 字符串
            localStorage.setItem("todos", JSON.stringify(value));
          },
        },
      },
      // 销毁前进行自定义事件的解绑
      beforeDestroy() {
        this.$off(['addTodo', 'checkAllTodo', 'clearAllTodo'])
      }
    };
    </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-edit {
      margin-right: 5px;
      background-color: skyblue;
      border: 1px solid rgb(102, 158, 180);
    }
    
    .btn-danger:hover {
      color: #fff;
      background-color: #bd362f;
    }
    
    .btn:focus {
      outline: none;
    }
    
    .todo-container {
      width: 600px;
      margin: 10px auto;
    }
    
    .todo-container .todo-wrap {
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 5px;
    }
    </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
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139

    MyHeader.vue 组件

    • 终端键入 npm i nanoid,安装 nanoid