# TodoMvc **Repository Path**: MonsterLQ/TodoMvc ## Basic Information - **Project Name**: TodoMvc - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-06-15 - **Last Updated**: 2021-01-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # TodoMVC ## 数据列表渲染 ### 有数据 > 直接渲染 ```html
  • ``` ### 无数据 > 在没有数据的时候让列表和底部操作隐藏 ```javascript v-if="todos.length" ``` > 这样需要给列表和底部都添加这个指令 > 如果想要只写一边的话,可以在这两个结构外包裹一个盒子,但是这样很不理想,平白无故多出一个盒子,这里`vue`提供了一个标签`template`,这个标签可以搭配`v-if`使用,并且页面不会显示这个标签 ```html ``` ## 添加任务 ### 页面初始化获得焦点 > 在标签中使用`autofocus`属性是不起作用的,这里使用自定义指令,然后在标签中使用`v-focus`即可 ```javascript directives: { focus: { inserted: function (el) { el.focus(); } } } ``` ### 任务项双击获取焦点 > 需要重新定义一个自定义属性,由于双击后获取焦点,所以就需要把设置的指令放到update中去。 > > 这样是不严谨的,会执行很多次。所以这里就利用了钩子函数的第二个参数`binding`,这个参数是一个对象,就是利用对象中的`value`属性,这个`value`属性是指令绑定的值,那么就需要使用这个指令的时候绑定值,然后进行判断 ```html v-todo-focus="currentTodo===item" ``` > 这里绑定的值是有意义的,判断点击的是不是当前项,判断的值是布尔值,正好可以用用来value值的判断 ```javascript todoFocus: { update(el, binding) { if (binding.value) { el.focus() } } } ``` > 在这个指令里面做了一个判断,binding.value只有一个是true,然后让当前获取焦点,并且只会执行一次 ### 添加任务 > 点击键盘回车添加任务 ```html ``` > 事件处理函数 1. 获取文本框数据 2. 不允许有非空数据 3. 添加完成后清空文本框 ```javascript methods:{ handleDown(e) { //e是事件对象 var title = e.target.value.trim(); //获取去除空格后的数据 //添加数据的id,没有数据id为1 var id = this.todos.length ? this.todos[this.todos.length - 1].id + 1 : 1; if (!title.length) return e.target.value = '' //数据为空不处理 this.todos.unshift({ //把数据添加到todos id, title, completed: false }) e.target.value = '' //清空文本框 } } } ``` > 这里获取数据还可以使用`v-model`数据双向绑定,同时还可以添加修饰符`.trim`-`v-model.trim`可以过滤空白字符 ## 任务状态改变 ### 单个任务状态改变 > 复选框双向数据绑定 ```html ``` > 列表根据数据中的状态添加样式 ```html
  • ``` > 复选框是双向数据绑定所以复选框状态改变后,数据也会改变,而列表中是否添加类名就是取决与数据中的状态,这样就达到了复选框状态改变同时列表的样式也会改变 ### 全选与全不选 1. 给checked绑定change事件 2. 获取checked的选中状态 3. 遍历所有数据,使状态与checked状态一致 ```html ``` ```javascript handleToggleAll(e) { var checked = e.target.checked //获取选中状态 this.todos.forEach(item => { item.completed = checked }) } ``` ### 切换联动 > 让全选复选框与任务列表联动 > > 思路:使用计算属性,用every去未每个todos数据遍历,结果会得到true或false,最后绑定到全选中 ```javascript toggleAll: { return this.todos.every(item => item.completed) } ``` ```html :checked="toggleAll" ``` ### 优化全选与联动功能 > 这两个功能可以进行优化,合并 > > 思路:联动的计算属性可以使用双向绑定,而完整版的计算属性中有两个属性get和set两个方法,一个是获取时调用,一个是设置时调用,这两个属性正好对应了当初给全选按钮绑定的change事件,所以在这里可以把change事件取消,把需要操作的写在计算属性的set中 1. 数据双向绑定 2. 在get中书写联动功能 3. 在set中书写全选功能 - 获取当前全选框的状态 - 给所有数据统一设置状态 ```javascript toggleAll: { get() { return this.todos.every(item => item.completed) }, set() { var checked = !this.toggleAll this.todos.forEach(item => { item.completed = checked }) } } ``` > 总结:设置联动状态主要利用的就是every这个方法,这个方法会为每个数据都执行操作,全部满足则返回true否则返回false,然后去判断todos里是否都是true,返回的是一个布尔值,把这个值绑定到全选框中就可以达到联动状态。 > > 在全选功能中,难点就是获取当前全选复选框的选中状态,既然给这个复选框进行了双向数据绑定,那么通过`this.toggleAll`就可以看到当前的状态,但是你要知道这个状态的来源就是计算属性的get方法中计算得来的(可以打印this.toggleAll来观察数据的变化)。在点击时状态就要改变所以那个时候的状态应该取非这才是所需要的状态。 ## 任务操作 ### 删除单个任务 1. 添加点击删除事件---需要参数id ```html ``` 2. 根据对应的参数id删除数据 ```javascript handleDel(id) { this.todos.forEach((item, i) => { if (item.id == id) { this.todos.splice(i, 1) } }) } ``` ###双击任务项添加编辑样式 > 思路:所有任务项只有当前点击后添加编辑样式.所以当前点击项等于数据的列表项就添加样式 1. 给label添加双击事件 ```html
  • ``` > 总结:在双击的时候把当前点击的列表项在数据中保存,当前双击保存的数据无论如何也就只有一项,然后每个列表项中的class都会有一个判定,当`currentTodo`等于我自己的时候,就会添加样式 ### 点击enter键或则失去焦点保存 1. 添加事件 ```html ``` 2. 获取输入框数据 3. 非空校验 - 为空时删除当前列表项 - 不为空时修改数据 4. 修改数据,并且取消`editing`样式 ```JavaScript handleSaveEdit(item, e) { var val = e.target.value.trim(); if (!val.length) { this.handleDel(item.id); //利用上面写好的删除方法 return } else { item.title = val; //把当前项的数据修改 this.currentTodo = null //去除样式 } } ``` ### 取消编辑 > 添加事件取消类名 ```html @keydown.esc="handleEsc" ``` ```javascript handleEsc() { this.currentTodo = null } ``` ### 删除完成项 1. 控制删除完成按钮的显示隐藏 > 可以书写`js`语法,在数据中只要有完成的任务,就会显示按钮 ```html v-if="todos.some(item=>item.completed)" ``` 2. 删除数据 ```html @click="clearAll" ``` ```JavaScript clearAll() { for (var i = 0; i < this.todos.length; i++) { if (this.todos[i].completed) { this.todos.splice(i, 1); i-- //这里一定要-- } } } ``` > 注意:在遍历数组中不可以直接删除数据,因为直接删除数据后,数据项会发生变化,此时的数据项和索引就会对不上。删除某个数据后,数据项会向前面移动,你拿之前的索引去找对应的数据项,肯定会出错,这个时候就需要`i--`,删除一个数据后不仅数据项往前移,同样也要使索引向前移 ## 本地持久化 > 本地持久化的时候,todos就不能是自己定义的todos,而是要从本地获取。 > > 每次数据变化都需要把最新的数据重新储存到本地,这里使用watch监听数据,发现数据变化就保存最新数据 ```javascript todos: JSON.parse(localStorage.getItem('todos') || '[]') //todos数据从本地获取 ``` ```javascript watch: { todos: { deep: true, //深度监听 handler(val) { localStorage.setItem('todos', JSON.stringify(val)) console.log(val); } } } ``` > 这里要注意的是,普通写法的监听只能监听普通类型的数据变化,对于引用类型的数据变化,是监听不到的,而这里的todos就是引用类型.所以就需要深度监听,写法会有不一样,todos被监听的数据不咋是函数,而是一个对象,这个对象里面有一些固定的属性,其中deep属性就是用来深度监听的,还有一个属性handler就是数据变化后的操作.属性名和写法都是固定的. ## 底部功能实现 ### 未完成数量计算 > 未完成数量是会根据操作而改变,所以用计算属性比较合适。 > > 计算属性里面用到了todos这个数据,所以只要todos数据发生改变都会重新计算 > > 思路:使用filter方法筛选出符合要求的新数组,然后返回新数组的长度就是未完成的数量 ```JavaScript computed: { index() { return this.todos.filter(item => !item.completed).length } } ``` > 在页面直接使用计算好的`index`即可 ### 过滤数据输出 > 根据点击的是否是all、active、completed来过滤显示需要的数据 思路: 1. 过滤数据可以使用filter进行过滤 2. 根据点击不同的按钮展示返回不同的数据,可以用switch分支,这需要在计算属性中计算后返回符合要的数据 3. 可以从hash地址获取当前点击的是那个按钮 ```javascript filterText: 'all' //在data中定义用来保存当前点击的是哪个按钮 ``` ```javascript filterTodos() { switch (this.filterText) { case 'active': return this.todos.filter(item => !item.completed) break; case 'completed': return this.todos.filter(item => item.completed) break; default: return this.todos break; } } ``` > 根据不同的值返回计算好的数据,这就需要把当初遍历数据的todos换为filterTodos ```javascript hashChange() { this.filterText = window.location.hash.substr(2) } ``` > 在methods方法中定义方法,获取地址栏中的hash值,并赋值给`filterText` ```javascript created() { this.hashChange() window.onhashchange = this.hashChange } ``` > 在页面数据初始化完成后执行一个事件,hash改变事件,当hash改变就要执行定义好的事件,值得一说的是:在页面数据初始化完成时要先调用一下,这样才能做到过滤数据的初始化. ### 按钮高亮 > 动态绑定class就可以了 ```html :class="{selected:filterText===''}" ``` > 等于号里面的就是当前hash名