前言
官方示例效果: todomvc.com/examples/re…
拿到这个案例之后你会怎么分析,先暂停3分钟,即便todo-list你已经做过很多次了。
我的第一个版本的todo-list,存在很多问题,例如代码的设计不合理,封装不到位等等, 我们看看官方源码是怎么设计的 sandBox
一、代码结构
1.1 DOM结构
todo-list 官方示例中关于html5的标签语义化和结构划分都非常合理。
<section> <header></header> <section></section> <footer></footer> </section>
1.2 样式结构处理
在处理一些样式的时候,注意功能单一原则, 也就是html结构中不应该去处理css样式,也就是在最佳实践中不使用style内联来处理样式,更推荐使用动态类名来处理。
<li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{ completed: todo.completed, editing: todo == editedTodo }" >
1.3 代码的命名规范
- 函数名采用驼峰命名规范
- 类名采用-来进行拼接
- 描述某个动作的采用的发生与否采用形容词 例如 complected 标记是否完成
二、代码设计
从功能出发分析代码设计
2.1 todo-list 的增删改查设计
和我设计的函数不同,官方示例给的函数传入的参数都是一个todo,具体todo的处理方式在函数内部进行。
emoveTodo: function (todo) { this.todos.splice(this.todos.indexOf(todo), 1); } editTodo: function (todo) { this.beforeEditCache = todo.title; this.editedTodo = todo; } doneEdit: function (todo) { if (!this.editedTodo) { return; } this.editedTodo = null; todo.title = todo.title.trim(); if (!todo.title) { this.removeTodo(todo); } }
2.2 状态模式下的list展示
todolist的展现存在三个状态 all actived complected
这里官方给的和我写的思路基本一致,设置状态变量state,和filterTodo 展示列表
var filters = { all: function (todos) { return todos; }, active: function (todos) { return todos.filter(function (todo) { return !todo.completed; }); }, completed: function (todos) { return todos.filter(function (todo) { return todo.completed; }); } }
2.3 本地存储的函数封装
于直接使用localStorage不同的是,示例中的localStorage进行了一次封装,这样有以下几个好处
- 不需要关注存储/读取数据过程中的一些类型转化
- 代码的可读性更高,函数之间耦合度更低
var STORAGE_KEY = "todos-vuejs-2.0"; var todoStorage = { fetch: function () { var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); todos.forEach(function (todo, index) { todo.id = index; }); todoStorage.uid = todos.length; return todos; }, save: function (todos) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); } };
2.4 复用功能的实现
关于input框聚焦功能的实现 ---自定义指令
这里扩展两个知识
- 自定义指令的第一个参数,是所绑定的元素
- 第二个参数binding.value 是所绑定的数据,这里的做法真的很巧妙
v-todo-focus="todo == editedTodo" // 使用指令 // 指令实现 directives: { "todo-focus": function(el, binding) { if (binding.value) { el.focus(); } } }
2.5 localstorage 的存储时机
存储
示例中采用的方法是watch监听todos,每次当todos发生变化的时候就直接存储
watch: { todos: { handler: function(todos) { todoStorage.save(todos); }, deep: true } },
读取
读取的初始化设置封装在todoStorage当中(特殊情况),而data中只含有读取操作
// app initial state data: { todos: todoStorage.fetch(), newTodo: "", editedTodo: null, visibility: "all" }
2.5 控制全选 / 全不选
全选全不选这里设计的也很巧妙我们先看dom结构的设计
采用v-model进行双向绑定
<input id="toggle-all" class="toggle-all" type="checkbox" v-model="allDone" />
勾选 或者 取消勾选 全选按钮都会触发 allDone的setter
// computed allDone get: function() { return this.remaining === 0; } set: function(value) { this.todos.forEach(function(todo) { todo.completed = value; }); }
2.6 数据设计
这一栏的话是看开发经验来说的,我的设计基本上也是保持一直
是否完成状态
- completed
标记每一个任务
- id
任务名称
- title
以前在做todolist的时候设计过另外一个状态,isEdit
这个我将其作为todo的一个属性,用来标记每一个任务是否处于一个修改的状态
这里的做法比我的设计要好
// 只需要一个editedTodo来标记当前处于编辑状态的任务即可 data: { editedTodo: null, },
相对给每一个todo添加isEdit属性来说,这个做法的好处有以下几点
- 无需维护多个任务处于Edit状态
- 内存占用(虽然微小,但是细节得注意)
2.7 切换状态的设计
展示列表一共有三个状态, all 、 active、completed
我的做法是设计一个方法去修改状态,而示例中的做法再一次打开的我的眼界
- 通过a标签的herf属性添加hash值
- 给window添加上hashchange事件的监听处理函数
- 每次hash发生改变的时候修改当前的状态
function onHashChange() { var visibility = window.location.hash.replace(/#/?/, ""); if (filters[visibility]) { app.visibility = visibility; } else { window.location.hash = ""; app.visibility = "all"; } } window.addEventListener("hashchange", onHashChange); onHashChange();