my practice
前言
官方示例效果: 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();