TODOMVC是vue官方文档的一个示例,以下的代码来自vue官方
罗列一下这么vue的示例总共有如下交互:
初始状态:↓
$实现--代码引用:通过todoMVC来学vue.js的使用
<section class="todoapp"> // <section> 标签定义了文档的某个区域,更多的是意味着这里面主要有文本元素 <header class="header"> <input class="new-todo" autofocus autocomplete="off" // 'off'意味着网页加载的时候不会自动获得焦点,必须用户自己点击这input placeholder="What needs to be done?"> </header> <section class="main" > <ul class="todo-list"> <li class="todo"> <div class="view"> <input class="toggle" type="checkbox"> <label> </label> // label元素本身没啥,关键点击了会触发事件,要绑定下面的input.edit呗 <button class="destroy"></button> </div> <input class="edit" type="text"> </li> </ul> </section> <footer class="footer"> <span class="todo-count"> <strong></strong>left // 计数用的 </span> <ul class="filters"> <li><a href="#/all" >All</a></li> // 提前用到了router路由功能 <li><a href="#/active" >Active</a></li> <li><a href="#/completed">Completed</a></li> </ul> <button class="clear-completed"> remaining"> Clear completed </button> </footer> </section>
细节:
1:这是纯静态的html,必须包括全部状态下的dom结构,控制这个dom用vuejs,展示dom用css
2:autocomplete(自动完成)表示:
用户在文本框输入前几个字母或是汉字的时候,该控件就能从存放数据的文本或是数据库里将所有以这些字母开头的数据提示给用户,供用户选择
这里没有数据库,自然不用自动完成(也是vue作者的良心代码)
3:li.todo为什么要有一个div包裹?因为label双击以后是要好input.edit'重合'的,怎么做到的呢?
那自然是通过给这俩啊,添加css样式:display:none或者display:block来'重叠啦',而且这俩应该是div.view和input.edit
因为编辑的时候自然是不想看到前面的checkbox和叉叉的啊
4:路由功能
这里之所以是用'/#/'来前缀只不过是给js处理的时候能好处理,其实是对后面的字符串赋值给变量,监听这个变量才转换list的状态的
----------------------------------------------------------------------------------------------------------------------------------------------------------
- 书写完new-todo增加至todos以active状态在list(all状态,active状态都有)下展示,此时左下角出现没完成的todo的计数↓
html:
<input class="new-todo" autofocus autocomplete="off" placeholder="What needs to be done?" v-model="newTodo" @keyup.enter="addTodo">
js--vue.data:
data: { todos: todoStorage.fetch(), // 初始值是fetch方法返回的,现在是: [] newTodo: '', // 零时变量 editedTodo: null, visibility: 'all' //初始值是在all状态的list }
js--外部方法:
var STORAGE_KEY = 'todos-vuejs-2.0' var todoStorage = { fetch: function () { var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]') // parse:从字符串中解析成json对象或者'[]' todos.forEach(function (todo, index) { todo.id = index }) todoStorage.uid = todos.length //此处第一次执行是 0;[].length = 0; return todos }, save: function (todos) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)) // 从todos中解析成字符串然后传入localStorage } }
js--vue.methods:
methods: { addTodo: function () { var value = this.newTodo && this.newTodo.trim() // this.newTodo指的是当前vue实例的data里的newTodo,trim()指的是去掉前后空格 if (!value) { return // 没填直接滚蛋 } this.todos.push({ // 压入todos里{id:1,title:'toUnderStandVue',completed:false} id: todoStorage.uid++, title: value, completed: false }) this.newTodo = '' // 重置临时变量 }
1:用v-model来绑定这个newTodo零时变量,为啥要绑定:
因为不想在触发事件的时候用getElementsByClass('new-todo')[0].val()来获得数据
2:用@keyup.enter监听回车事件,回车触发addtodo事件
重点:todos数组也是变量,存着有什么用?又看不到,别急请看3,4
3:
watch: { // 观察者时刻准备着 todos: { // 眼中钉todos变量 handler: function (todos) { // 敢动我就毙了你! todoStorage.save(todos) // 好,压入localStroage }, deep: true } },这就是观察者了,我们知道todos初始值是[],当它有变化(这里就是被push东西)的时候,直接跑handler方法,就存了一个 存了一个对象的数组的字符串 : [{"id":0,"title":"toUnderStandVue","completed":false}]
存在本地是为了能刷新页面数据不会丢失的实现
4:dom展现todos
html改造:
<li v-for="todo in filteredTodos" class="todo" ..
filteredTudos是作者用js过滤的list,其实filteredTudos是不确定的,因此用vue的computed来计算当前的是哪个状态,当然前面data里的visibility初始值是'all',计算出来的值就是:{id:1,title:'toUnderStandVue',completed:false}
之后的就是渲染dom了
js:- 点击todos里的一个todo的checkbox,此todo变complete状态,在list(complete状态)下展示,此时计数归零,且右下角出现清除完成的todo按钮↓
B实现
1:li.todo本身的变化
<input class="toggle" type="checkbox" v-model="todo.completed"> <li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{ completed: todo.completed, editing: todo == editedTodo }">
<input class="toggle" type="checkbox" v-model="todo.completed">
<li v-for="todo in filteredTodos" class="todo" :key="todo.id"
:class="{ completed: todo.completed, editing: todo == editedTodo }">
把自身的completed属性和input绑定,点一下就触发了completed = true,结果li就动态添加了completed样式,label有变化,
.todo-list li.completed label { color: #d9d9d9; text-decoration: line-through; }注意叉叉还存在:
.todo-list li:hover .destroy { display: block; }2:计数的变化:
<span class="todo-count"> <strong>{{ remaining }}</strong> {{ remaining | pluralize }} left </span>
<span class="todo-count"> <strong>{{ remaining }}</strong> {{ remaining | pluralize }} left </span>
computed: { remaining: function () { return filters.active(this.todos).length },
var filters = { active: function (todos) { return todos.filter(function (todo) { return !todo.completed }) } }由于计数本身也不确定,也需要计算,这里算的就是此时所有的元素的!completed数量;
filter就是让数组里的每一个元素都跑一边回调函数:回调函数可以有三个参数:当前元素,当前元素的索引和当前的数组对象。
相当如用一个for把数组遍历了一边且返回过滤了(这里就是元素里的completed是true)的数组,剩下的自然是还没做的todo3:clear completed的按钮显示:
<button class="clear-completed"
@click="removeCompleted" v-show="todos.length > remaining">Clear completed </button>显示的逻辑是:只要todos比提醒的还要长(意味着数组里肯定还有做完了的)就显示,其实也可以像计数一样写一个计算的已完成的变量,但是作者不按套路出牌让我们又学到了一种v-show的用法,真是太暖心心了.
----------------------------------------------------------------------------------------------------------------------------------------------------------
- 再次点击同一个todo的checkbox,此todo变回active状态并在list(all状态,active状态)下展示(仿佛回到了addtodo完事儿以后的样子)↓
C实现--代码:无
完全反过来的重复B实现↑
好了这是为什么呢???
完全是基于这个this.tudos的本身的变化,而且仅仅是因为里面的completed:由false换成了true,看,双向绑定多帅气
----------------------------------------------------------------------------------------------------------------------------------------------------------
D实现--代码:
<input class="toggle-all" type="checkbox" v-model="allDone">allDone又是一个计算过来的变量↓
computed: { allDone: { get: function () { // 点一下todo前面的input执行get(true),实质也是改变了completed才触发 return this.remaining === 0 }, set: function (value) { // 点击自己执行set(true),改动的又是todos里全部的completed this.todos.forEach(function (todo) { todo.completed = value }) } } }绑定的目的显然是想手动改变数据,然后数据又改变dom.看,双向绑定多帅气
----------------------------------------------------------------------------------------------------------------------------------------------------------
- 做完了没事情做?再添加一个呗(注意到完成了的todo在上边,新来的todo在下边)↓
- 不小心刷新了页面,我的todo还在?自然是在h5的locationStore存下来啦 回看A实现重点3
- all状态的list有点乱,我要看看完成了什么?点击footer的completed看看↓
E实现--代码:
// handle routing function onHashChange () { var visibility = window.location.hash.replace(/#\/?/, '') 前面的A实现说过visibolity是拿来方便取的 if (filters[visibility]) { // 如果当前的路由的列表(#/Completed todos不为空) app.visibility = visibility //传参给vue实例:var app = vue(...); } else { window.location.hash = '' app.visibility = 'all' } }
window.addEventListener('hashchange', onHashChange) 全局监听路由事件 onHashChange()传给实例干嘛呢?自然是要计算这个过滤过的tudos:filteredTodos
自然li就会都显示completed的li,还记得这个吗?
<li v-for="todo in filteredTodos" class="todo"
----------------------------------------------------------------------------------------------------------------------------------------------------------
- 那我再看看active的todo吧↓
- 做完了想清除?好那回到list(all)再点击clear completed把做完的todo删掉吧↓
F实现--代码:
<button class="clear-completed" @click="removeCompleted"
v-show="todos.length > remaining">Clear completed </button>
removeCompleted: function () { this.todos = filters.active(this.todos) }
var filters = { active: function (todos) { return todos.filter(function (todo) { return !todo.completed //还没完成的todos }) }
显然是直接给todus赋值为还剩下的没完成的
----------------------------------------------------------------------------------------------------------------------------------------------------------
- 我想改一下这个todo,名字太土了,双击它(作者很贴心,编辑框一旦失去焦点会自动完成)↓
G实现--代码:
directives: { // 这是vue的指令的编写,只要true就会触发 'todo-focus': function (el, value) { if (value) { el.focus() //el指的是用了这个指令的当前的dom元素 } } }
<label @dblclick="editTodo(todo)">{{ todo.title }}</label> //注意到只有双击了label,触发了input <input class="edit" type="text" v-model="todo.title" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)"> editTodo: function (todo) { this.beforeEditCache = todo.title //用临时变量存信息 this.editedTodo = todo // 使临时变量editedTudo赋值为tudo,用处为触发指令:-focus }, doneEdit: function (todo) { if (!this.editedTodo) { // 如果这个时候临时对象空了,对不起,无法编辑 return } this.editedTodo = null todo.title = todo.title.trim() // 去掉前后空格 if (!todo.title) { this.removeTodo(todo) // 如果编辑成了空,直接删除,用this应该是作用域的问题 } }, cancelEdit: function (todo) { this.editedTodo = null todo.title = this.beforeEditCache //放弃编辑,是键盘esc退出触发的事件 }
- 好了,我现在一个都不想做了,删掉这个挠人的todo吧(回到初始状态)↓
H实现--代码:
<button class="destroy" @click="removeTodo(todo)"></button>
removeTodo: function (todo) { this.todos.splice(this.todos.indexOf(todo), 1) },显然先传参,然后删除这个元素;在this.tudos删除
----------------------------------------------------------------------------------------------------------------------------------------------------------