前言
TodoMVC是一个示例项目,它使用目前流行的不同JavaScript框架的来实现同一个Demo,来帮助你熟悉和选择最合适的前端框架。学习框架最直接有效的方式就是上手练习,接下来我们将用Vue.js来完成TodoMVC的示例。
官网地址:https://todomvc.com/
一、准备
在 Vscode 软件在打开集成终端
- 输入
vue cerate 项目名
创建项目 - 输入
cd 项目名
进入创建好的项目内 - 输入
npm run serve
启动项目 - 创建
TodoDemo.vue
- 将
TodoDemo.vue
引入App.vue
二、创建
1.编写TodoMvc基本结构
代码如下(示例):
<template>
<div class="todo-demo">
<h1>TODOS</h1>
<div class="todo-form">
<input type="text">
<button>添加</button>
</div>
<div class="todo-content">
<ul>
<li> </li>
</ul>
</div>
<div class="todo-foot">
<span>1 item left</span>
<div class="type-btns">
<button>all</button>
<button>active</button>
<button>completde</button>
</div>
<button>clear completde</button>
</div>
</div>
</template>
<script>
export default {
data(){
return {
todos: [
{
id:1,
text:"律师函警告",
done: false
},
{
id:2,
text:"喜欢唱,跳,rep,篮球",
done: true
},
{
id:3,
text:"你干嘛,哎呀",
done: false
}
]
}
}
}
</script>
<style>
.todo-demo {
width: 400px;
margin: 0 auto;
}
.todo-foot{
display: flex;
justify-content: space-between;
}
</style>
2.功能实现
- 1) 使用
v-for
实现展示功能
代码如下:
<div class="todo-content">
<ul>
<li> <li v-for="todo in todos" :key="todo.id">{{todo.text}}</li></li>
</ul>
</div>
- 2) 使用
@click
实现删除功能
代码如下:
<div class="todo-content">
<ul>
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox">{{todo.text}}
<span @click="del(todo.id)">×</span></li> <!--点击 x 实现删除功能-->
</ul>
</div>
export default {
data(){...}
},
methods: {
//删除功能
del(id){
this.todos = this.todos.filter(todo => todo.id !== id)
}
}
}
- 3) 使用
v-model
实现复选框修改事件
代码如下:
<div class="todo-content">
<ul>
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" v-model="todo.done">{{todo.text}}
<span @click="del(todo.id)">×</span></li>
</ul>
</div>
export default {
data(){
return {
todos: [
{
id:1,
text:"律师函警告",
done: false
},
{
id:2,
text:"喜欢唱,跳,rep,篮球",
done: true
},
{
id:3,
text:"你干嘛,哎呀",
done: false
}
]
}
},
methods: {
del(id){
this.todos = this.todos.filter(todo => todo.id !== id)
}
}
}
- 3.1) 使用
:checked 和@change
实现复选框修改事件
代码如下:
<div class="todo-content">
<ul>
<li v-for="todo in todos" :key="todo.id">
<!-- <input type="checkbox" v-model="todo.done"> -->
<input type="checkbox" :checked="todo.done" @change="change">
{{todo.text}}
<span @click="del(todo.id)">×</span>
</li>
</ul>
</div>
export default {
data(){
return {...},
methods: {
// 删除功能
del(id){...},
// 复选框的修改事件
change(id){
const currentTodo = this.todos.find(todo => todo.id === id)
currentTodo.done = !currentTodo.done
}
}
}
- 3.2)使用
:class
实现复选框选择状态下画横线
代码如下:
<div class="todo-content">
<ul>
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<!-- <input type="checkbox" :checked="todo.done" @change="change"> -->
<span :class="{done: todo.done}">{{todo.text}} </span>
<!-- 复选框选择状态下,我们使用 :class 的绑定中的对象的写法来实现 -->
<span @click="del(todo.id)">×</span>
</li>
</ul>
</div>
<style>
/* 选择状态的样式 */
.todo-content ul li span.done{
text-decoration: line-through;
color: #ccc;
}
</style>
- 4)使用
v-model.trim @click
实现添加功能,@keyup.enter:
使用键盘回车完成添加
代码如下:
<div class="todo-form">
<!-- 事件修饰符 .enter @keyup.enter:键盘上回车 完成添加-->
<input type="text" v-model.trim="todoText" @keyup.enter="submit">
<button @click="submit">添加</button>
</div>
data(){
return {
todos: [...],
todoText: "",//默认为空
}
},
methods: {
// 添加功能
submit(){
const { todoText } = this
console.log(todoText)
if(todoText){
// Date().getTime() 确保添加完成后的 id 不同
this.todos.push({ id: new Date().getTime(), text: todoText})
this.todoText = '';// 添加完成后清空输入框
}
},
}
- 5)使用计算属性
computed
以箭头函数实现动态未完成事件的更新
代码如下:
<div class="todo-foot">
<!-- 实现动态 item left的更新 -->
<span>{{activeTodoNums}}item left</span>
</div>
export default {
data(){
return {
todos: [... {... done: false}],
todoText: "",//默认为空
}
},
// 使用计算属性:computed 实现动态的未完成事件
computed: {
activeTodoNums(){
// 方法一:
// return this.todos.reduce( function(res, todo){
// if(!todo.done){
// res ++
// }
// return res
// },0)
// 使用箭头函数 判断 done 为false有多少 !todo.done 如果为false 则 ++res
return this.todos.reduce((res, todo) => (!todo.done ? ++res : res),0)
}
}
- 5.1)使用
:class @click computed
实现 all active completde 的选择,并且改变颜色
代码如下:
<div class="type-btns">
<button :class="{'btn-active': type === 'all'}" @click="type = 'all'">all</button>
<button :class="{'btn-active': type === 'active'}" @click="type = 'active'">active</button>
<button :class="{'btn-active': type === 'completde'}" @click="type = 'completde'">completde</button>
</div>
export default {
data(){
return {[...]
todoText: "",//默认为空
type: 'all',// 规定点击的的是 all | active | completde
}
},
// 使用计算属性:computed 实现动态的未完成事件
computed: {
activeTodoNums(){...},
showTodos(){
return []
}
},
}
<style>
...
/* 点击效果 */
.btn-active {
color: red;
}
</style>
- 5.2)使用
computed 与 JavaScript中的 const
实现 all active completde 的选择的展示
代码如下:
<div class="todo-content">
<ul>
<li v-for="todo in showTodos" :key="todo.id"> <!--重新选择遍历对象为:showTodos-->
...
</li>
</ul>
</div>
export default {
data(){
return {[...]
todoText: "",//默认为空
type: 'all',// 规定点击的的是 all | active | completde
}
},
// 使用计算属性:computed 实现动态的未完成事件
computed: {
activeTodoNums(){...},
showTodos(){
const {todos, type} = this
// 使用箭头函数 对 type 的值 进行判断,判断它是为:all | active | completde
return todos.filter(todo => type === 'all' ? true : type === 'active' ? !todo.done : todo.done)
}
},
}
- 5.2)使用
@click
实现 clear completde 清空已经完成的任务
代码如下:
```handlebars
<button @click="clear">clear completde</button>
computed: {
export default {
data(){
return {[...]
todoText: "",//默认为空
type: 'all',// 规定点击的的是 all | active | completde
}
},
// 使用计算属性:computed 实现动态的未完成事件
computed: {
activeTodoNums(){...},
clear(){
this.todos = this.todos.filter(todo => !todo.done)
},
}
- 6)使用
v-if v-elese @dblclick :value @blur v-model.lazy
实现双击文字进行编辑功能
代码如下:
<div class="todo-content">
<ul>
...
<!-- template 不会被渲染成所有类 -->
<template v-if="editId !== todo.id">
<!-- 判断 editId 与 todo.id 是否相同 不相同展示内容,相同展示输入框 -->
...
</template>
<!-- 双击出输入框效果,且原文字内容展示到输入框 -->
<!-- @blur="changeTodoText" 失去交点的时候关闭输入框-->
<!-- v-model.lazy 失去交点的时候完成修改 -->
<input v-else type="text" @blur="changeTodoText" v-model.lazy="todo.text"/>
</li>
</ul>
</div>
<script>
export default {
data() {
return {
todos: [...],
todoText: "", //默认为空
type: "all", // 规定点击的的是 all | active | completde
editId:' ', // 定义一个可编辑 id,默认页面只能出现一个 id 当 id相同时则进行编辑
};
},
// 使用计算属性:computed 实现动态的未完成事件
computed: {
activeTodoNums() {...},
showTodos() {...},
},
methods: {
// 删除功能
del(id) {... },
// 复选框的修改事件
// change(id){...},
// 添加功能
submit() {...},
// 实现clear completde 清空已经完成的任务
clear() {...},
changeTodoText(){
// 失去交点关闭输入框
this.editId = 0
}
},
};
</script>
对于上代码:功能虽然已实现但仍然存在着功能缺陷
<div class="todo-content">
<ul>
...
<!-- 双击事件 -->
<span @dblclick="handleDblclick(todo.id)" :class="{ done: todo.done }">
{{ todo.text }}
</span>
...
</li>
</ul>
</div>
<script>
export default {
...
changeTodoText(){
// 失去交点关闭输入框
this.editId = 0
},
// 错误展示
handleDblclick(id){
this.editId = id
console.log(this.$refs.editInp[0])
this.$refs.editInp[0].focus()
}
},
};
</script>
错误分析:
- 如果修改完了 马上让输入框获得焦点话, 会出报错,提示 focus of undefined
- 因为 当双击的时候编辑 id 被修改, 输入框的出现, 所以获得焦点的方法和 输入框的出现起了冲突
- 入框的出现和输入框获取焦点相当于同时执行的。获取焦点方法是无法生效的。
- 可以使用
settimeout
做个延迟执行 - vue 官方提供了
$nextTick
错误修改:
<div class="todo-content">
<ul>
...
<!-- 双击事件 -->
<span @dblclick="handleDblclick(todo.id)" :class="{ done: todo.done }">
{{ todo.text }}
</span>
...
</li>
</ul>
</div>
<script>
export default {
...
changeTodoText(){
// 失去交点关闭输入框
this.editId = 0
},
// 错误展示
handleDblclick(id) {
this.editId = id;
// console.log(this.$refs.editInp[0])
// 如果修改完了 马上让输入框获得焦点话, 会出报错,提示 focus of undefined
// 因为 当双击的时候编辑 id 被修改, 输入框的出现, 所以获得焦点的方法和 输入框的出现起了冲突
// 输入框的出现和输入框获取焦点相当于同时执行的。获取焦点方法是无法生效的。
// 可以使用 settimeout 做个延迟执行
// vue 官方提供了 $nextTick
// 方法一:
// setTimeout( () => {
// this.$refs.editInp[0].focus()
// },5)
// }
// 方法二:
// nextTick作用:在data更新后,马上想要对 data 相关的 dom 节点进行操作的话,可能会失败。可以使用:nextTick来解决
this.$nextTick(() => {
// 循环里面设置了 ref 的话 需要使用 [0] 获取
// this.$resf.名
// 当在 v-for 循环里面使用 ref的话需要注意 ref获取结果时会是一个数组,需要添加 [0] 获取
// 当 ref定义的名字是相同的话,那么获取的结果会是一个数组 得到的是所有的 dom 节点
this.$refs.editInp[0].focus();
});
},
},
};
</script>
- 7)使用
v-model computed
实现点击的左时将所有任务设置成已完成
代码如下:
<div class="todo-form">
...
<!-- 实现点击的左时将所有任务设置成已完成 -->
<input type="checkbox" v-model="isAllChecked">
...
</div>
<script>
export default {
data() {...},
// 使用计算属性:computed 实现动态的未完成事件
computed: {
activeTodoNums() {...},
showTodos() {...},
// 点击的左时将所有任务设置成已完成
isAllChecked: {
get() {
return this.todos.every( (todo) => todo.done);
},
set(val) {
this.todos.forEach( todo => {
todo.done = val
});
},
}
},
methods: {...},
};
</script>
总结
- 事件处理
- 按键修饰符:键盘事件的修饰符 @keyup.enter
- .enter
- 按键修饰符:键盘事件的修饰符 @keyup.enter
- class 与 style 的绑定
- class的绑定
- 对象语法 :
{a:true}
- 数组语法 :
{‘a’,'b'}
- 数组对象语法 :
{‘a’,{b:true}}
- 对象语法 :
- class的绑定
- 对象
{width: '200px'}
- 对象
- class的绑定
- 条件渲染
- v-if v-else v-eles-if
- v-show
- 列表渲染:
v-for
:key
- 它可以在
template
上进行循环
- 表单的输入绑定:
v-model
- 修饰符:
.trim
自动清除 v-model 后面的左右值.lazy
失去交点的时候才会发生改变.number
交 data 的值转换成 数学 使用:Number 转换
- 它是
value
已经input
事件的简化版本
- 修饰符:
- 计算属性:
computed
- 当你想要的内容可以利用现有的 data 通过一些运算得到的话,那么我们就可以设置一个计算属性
- ref
- vue 提供了一种可以获取元素,真实 dom 节点的方法:
this.$refs.名
- 当在 v-for 循环里面使用 ref的话需要注意 ref获取结果时会是一个数组,需要添加 [0] 获取
- 当 ref定义的名字是相同的话,那么获取的结果会是一个数组 得到的是所有的 dom 节点
- vue 提供了一种可以获取元素,真实 dom 节点的方法:
nextTick
作用:- 在data更新后,马上想要对 data 相关的 dom 节点进行操作的话,可能会失败。可以使用:nextTick来解决
完整项目代码 TodoDemo.vue
<template>
<div class="todo-demo">
<h1>TODOS</h1>
<div class="todo-form">
<!-- 事件修饰符 .enter -->
<!-- 实现点击的左时将所有任务设置成已完成 -->
<input type="checkbox" v-model="isAllChecked">
<input type="text" v-model.trim="todoText" @keyup.enter="submit" />
<button @click="submit">添加</button>
</div>
<div class="todo-content">
<ul>
<li v-for="todo in showTodos" :key="todo.id">
<!--重新选择遍历对象为:showTodos -->
<!-- template 不会被渲染成所有类 -->
<template v-if="editId !== todo.id"
><!-- 判断 editId 与 todo.id 是否相同 不相同展示内容,相同展示输入框 -->
<input type="checkbox" v-model="todo.done" />
<!-- <input type="checkbox" :checked="todo.done" @change="change"> -->
<!-- 双击事件 -->
<span
@dblclick="handleDblclick(todo.id)"
:class="{ done: todo.done }"
>{{ todo.text }}
</span>
<!-- 复选框选择状态下,我们使用 :class 的绑定中的对象的写法来实现 -->
<span @click="del(todo.id)">×</span>
</template>
<!-- 双击出输入框效果,且原文字内容展示到输入框 -->
<!-- @blur="changeTodoText" 失去交点的时候关闭输入框-->
<!-- v-model.lazy 失去交点的时候完成修改 -->
<!-- 输入框自动 或得 交点的话 1. autofocus 属性 2. 使用原生 dom.focus() -->
<input
v-else
type="text"
@blur="changeTodoText"
v-model.lazy="todo.text"
ref="editInp"
/>
</li>
</ul>
</div>
<div class="todo-foot">
<!-- 实现动态 item left的更新 -->
<span>{{ activeTodoNums }}item left</span>
<div class="type-btns">
<button :class="{ 'btn-active': type === 'all' }" @click="type = 'all'">
all
</button>
<button
:class="{ 'btn-active': type === 'active' }"
@click="type = 'active'"
>
active
</button>
<button
:class="{ 'btn-active': type === 'completde' }"
@click="type = 'completde'"
>
completde
</button>
</div>
<button @click="clear">clear completde</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
todos: [
{
id: 1,
text: "律师函警告",
done: false,
},
{
id: 2,
text: "喜欢唱,跳,rep,篮球",
done: true,
},
{
id: 3,
text: "你干嘛,哎呀",
done: false,
},
],
todoText: "", //默认为空
type: "all", // 规定点击的的是 all | active | completde
editId: 0, // 定义一个可编辑 id,默认页面只能出现一个 id 当 id相同时则进行编辑
};
},
// 使用计算属性:computed 实现动态的未完成事件
computed: {
activeTodoNums() {
// 老方法
// return this.todos.reduce( function(res, todo){
// if(!todo.done){
// res ++
// }
// return res
// },0)
// 使用箭头函数 判断 done 为false有多少 !todo.done 如果为false 则 ++res
return this.todos.reduce((res, todo) => (!todo.done ? ++res : res), 0);
},
showTodos() {
const { todos, type } = this;
// 使用箭头函数 对 type 的值 进行判断,判断它是为:all | active | completde
return todos.filter((todo) =>
type === "all" ? true : type === "active" ? !todo.done : todo.done
);
},
// 点击的左时将所有任务设置成已完成
isAllChecked: {
get() {
return this.todos.every( (todo) => todo.done);
},
set(val) {
this.todos.forEach( todo => {
todo.done = val
});
},
}
},
methods: {
// 删除功能
del(id) {
this.todos = this.todos.filter((todo) => todo.id !== id);
},
// 复选框的修改事件
// change(id){
// const currentTodo = this.todos.find(todo => todo.id === id)
// currentTodo.done = !currentTodo.done
// },
// 添加功能
submit() {
const { todoText } = this;
console.log(todoText);
if (todoText) {
// Date().getTime() 确保添加完成后的 id 不同
this.todos.push({ id: new Date().getTime(), text: todoText });
this.todoText = ""; // 添加完成后清空输入框
}
},
// 实现clear completde 清空已经完成的任务
clear() {
this.todos = this.todos.filter((todo) => !todo.done);
},
changeTodoText() {
// 失去交点关闭输入框
this.editId = 0;
},
handleDblclick(id) {
this.editId = id;
// console.log(this.$refs.editInp[0])
// 如果修改完了 马上让输入框获得焦点话, 会出报错,提示 focus of undefined
// 因为 当双击的时候编辑 id 被修改, 输入框的出现, 所以获得焦点的方法和 输入框的出现起了冲突
// 输入框的出现和输入框获取焦点相当于同时执行的。获取焦点方法是无法生效的。
// 可以使用 settimeout 做个延迟执行
// vue 官方提供了 $nextTick
// setTimeout( () => {
// this.$refs.editInp[0].focus()
// },5)
// }
this.$nextTick(() => {
// 循环里面设置了 ref 的话 需要使用 [0] 获取
this.$refs.editInp[0].focus();
});
},
},
};
</script>
<style>
.todo-demo {
width: 400px;
margin: 0 auto;
}
.todo-foot {
display: flex;
justify-content: space-between;
}
.todo-content ul {
list-style: none;
padding-left: 0;
}
.todo-content ul li {
display: flex;
}
.todo-content ul li span {
user-select: none;
}
/* 复选框选择状态的样式 */
.todo-content ul li span.done {
text-decoration: line-through;
color: #ccc;
}
/* 点击效果 */
.btn-active {
color: red;
}
</style>