最初代码
简单搭一个vue2的小项目
App.vue
html
<template>
<div id="app">
<!-- 容器 -->
<div class="todo-container">
<div class="todo-wrap">
<!-- 头部 -->
<MyHeader :addTodo="addTodo"></MyHeader>
<!-- 主要内容区域 -->
<List :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></List>
<!-- 页尾 -->
<MyFooter :todoList="todoList" :checkAll="checkAll" :deleteAllDone="deleteAllDone"></MyFooter>
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue';
import MyFooter from './components/MyFooter.vue';
import List from './components/List.vue';
export default {
name: 'App',
components: {
MyHeader, MyFooter, List
},
data() {
return {
todoList: JSON.parse(localStorage.getItem('todoList')) || []
}
},
methods: {
addTodo(todoObj) {
// console.log('我是App组件,我收到了数据x',todoObj);
this.todoList.unshift(todoObj)
},
checkTodo(id) {
// console.log('我是app',id);
//在这里改变数据,从而依次去改变儿子的数据,因为是父传子
this.todoList.forEach((todo) => {
if(todo.id === id) {
todo.done = !todo.done;
}
})
},
deleteTodo(id) {
this.todoList= this.todoList.filter((todo) => {
return todo.id != id;
})
},
checkAll(flag) {
this.todoList.forEach((todo) => {
todo.done = flag;
})
},
deleteAllDone() {
this.todoList = this.todoList.filter((todo) => {
return !todo.done;
})
}
},
watch: {
todoList: {
deep:true,
handler(value) {
localStorage.setItem('todoList',JSON.stringify(this.todoList));
}
}
}
}
</script>
各个组件
MyHeader
html
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add" />
</div>
</template>
<script>
import { nanoid } from 'nanoid'
export default {
name: 'MyHeader',
props: ['addTodo'],
data() {
return {
title: ''
}
},
methods: {
add(e) {
if (this.title.trim() != '') {
const todoObj = { id: nanoid(), title: this.title, done: false };
this.addTodo(todoObj);
this.title = ''
}
}
}
}
</script>
List.vue
bash
<template>
<div><!-- 主要内容区域 -->
<ul class="todo-main">
<Item v-for="todoObj in todoList" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
</ul>
</div>
</template>
<script>
import Item from './Item.vue';
export default {
name: 'List',
components: { Item },
props: ['todoList','checkTodo','deleteTodo']
}
</script>
Item.vue
bash
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</div>
</template>
<script>
export default {
name: 'Item',
props: ['todo','checkTodo','deleteTodo'],
methods: {
handleCheck(id) {
//通知App取反是否勾选
this.checkTodo(id);
},
handleDelete(id) {
if(confirm('确定删除吗?')) {
this.deleteTodo(id);
}
}
}
}
</script>
MyFooter.vue
bash
<template>
<!-- 页尾 -->
<div class="todo-footer" v-show="total > 0">
<label>
<input type="checkbox" v-model="isAll" />
</label>
<span>
<span>已完成{{ doneNum }}</span> / 全部{{ total }}
</span>
<button class="btn btn-danger" @click="deleteDoneTodo">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: 'MyFooter',
props: ['todoList', 'checkAll', 'deleteAllDone'],
methods: {
deleteDoneTodo() {
this.deleteAllDone();
}
},
computed: {
doneNum() {
return this.todoList.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
},
total() {
return this.todoList.length;
},
isAll: {
get() {
return this.doneNum === this.total && this.total > 0;
},
set() {
this.checkAll(!this.isAll);
}
}
}
}
</script>
小总结
这里写的东西,都是比较原始的,像组件的通信这些,使用的是最原始的props
props按理来说,用于父和子之间的传递,所以如果如果是兄弟组件,只能两个兄弟组件通过父组件,来共享数据,或者说爷孙关系的组件的话,还得通过爷 -> 父 -> 子这样传,之后的完善还是很有必要的
浏览器本地存储
我自己也有写浏览器相关的记录
为什么要和存储有关,就是因为如果我们刷新页面的话,我们这个项目没有通过mysql进行数据的交互,我们就得依赖于本地存储,刷新页面的话,我们这个项目就不会存储我们之前的值
实现的方式,先总结来看,就是我们对todoList进行监视,如果todoList修改了,我们就要去重新赋值本地存储中的数据,这里的修改包括增删改
注意: 我们watch函数对对象的监视,只是浅浅的监视,不会监视对象属性的变化,如果要监视对象上属性的变化,需要开启深度监视,这一点在我博客的Vue基础那一篇中有记录
需要修改的代码,就这两行,我们监视的时候,需要开启deep: true
然后每次修改了重新赋值,需要注意的是,我们得使用JSON.stringify()把对象转换成字符串,这样我们才能看的懂
还有就是展示的时候,如果本来是空值的话,JSON.parse()返回的是null,所以我们得使用 ||
后面跟一个空数组,不然我们在其他页面使用 todoList.length会报错,相当于 null.length
会报错
组件通信修改
对于App.vue 和 Item.vue这两个组件之间的通信,原先我们的写法是
App -> List -> Item
需要通过List,这里我们通过全局事件总线进行修改
现在main.js中导入$bus
在App.vue上,我们给$bus定义事件
bash
mounted() {
this.$bus.$on('checkTodo',this.checkTodo);
this.$bus.$on('deleteTodo',this.deleteTodo);
},
beforeDestroy() {
this.$bus.$off('checkTodo');
this.$bus.$off('deleteTodo');
}
记得写解绑的代码
这里的回调函数用之前的就行
并且删除在List.vue
关于props传递的函数checkTodo
,deleteTodo
在Item.vue
中,我们发送数据,通过$bus发送数据
bash
handleCheck(id) {
//通知App取反是否勾选
this.$bus.$emit('checkTodo',id);
},
handleDelete(id) {
if(confirm('确定删除吗?')) {
this.$bus.$emit('deleteTodo',id);
}
},
发送数据便完成
编辑按钮
添加一个编辑按钮
整体代码的逻辑就是
我们点击编辑按钮,然后左边的title会呈现输入框的样式,如下
并且编辑按钮会消失
失去焦点之后,会自动更新todoList的数据,更新数据
vue
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
<span v-show="!todo.isEdit">{{todo.title}}</span>
<input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputEdit">
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit" >编辑</button>
</li>
</div>
...
handleEdit(todo) {
if(todo.hasOwnProperty('isEdit')) {
todo.isEdit = true;
} else {
//给todo绑定一个响应式的属性isEdit,动态追加属性
this.$set(todo,'isEdit',true);
}
},
我们给todo动态的绑定的一个属性,并且加入了判断是否有isEdit,避免重复添加属性
失去焦点之后,我们就更新数据
vue
<!-- 输入框代码 -->
<input type="text"
v-show="todo.isEdit"
:value="todo.title"
@blur="handleBlur(todo,$event)"
ref="inputEdit">
...
handleBlur(todo,e) {
todo.isEdit = false;
if(!e.target.value.trim()) {
alert('输入不能为空!');
return;
}
this.$bus.$emit('updateTodoEdit', todo.id,e.target.value)
}
需要注意的是,我们在input上拿取input输入的值的时候,需要给方法传$event,我们在方法内部使用e.target.value拿取输入内容
点击输入框,自动获取焦点
我们有这样一个需求,就是我们点击编辑之后,但是我不想改,点击其他地方,可以继续修改
但是我们写的代码,必须点击输入框然后再失去焦点才行,所以我们这里得自动获取焦点
那么谁去获取焦点呢? input输入框,所以在点击编辑按钮的时候,让input获取焦点
vue
handleEdit(todo) {
if(todo.hasOwnProperty('isEdit')) {
todo.isEdit = true;
} else {
//给todo绑定一个响应式的属性isEdit,动态追加属性
this.$set(todo,'isEdit',true);
}
this.$nextTick(() => {
this.$refs.inputEdit.focus();
})
}
值得注意的是,我们的input输入框显示与否,是根据todo.isEdit的值,进行显示与否的,所以handleEdit前面的代码是去显示input,如果我们直接让input输入框获取焦点,此时页面上没有input输入框,所以我们的需求是在dom刷新之后,在去获取焦点,我们就使用到nextTick(),他会在数据更新之后,在执行nextTick里边的回调函数
完整代码
vue
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
<span v-show="!todo.isEdit">{{todo.title}}</span>
<input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputEdit">
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit" >编辑</button>
</li>
</div>
</template>
<script>
export default {
name: 'Item',
props: ['todo'],
methods: {
handleCheck(id) {
//通知App取反是否勾选
this.$bus.$emit('checkTodo',id);
},
handleDelete(id) {
if(confirm('确定删除吗?')) {
this.$bus.$emit('deleteTodo',id);
}
},
handleEdit(todo) {
if(todo.hasOwnProperty('isEdit')) {
todo.isEdit = true;
} else {
//给todo绑定一个响应式的属性isEdit,动态追加属性
this.$set(todo,'isEdit',true);
}
this.$nextTick(() => {
this.$refs.inputEdit.focus();
})
},
handleBlur(todo,e) {
todo.isEdit = false;
if(!e.target.value.trim()) {
alert('输入不能为空!');
return;
}
this.$bus.$emit('updateTodoEdit', todo.id,e.target.value)
}
}
}
</script>
App.vue
vue
<template>
<div id="app">
<!-- 容器 -->
<div class="todo-container">
<div class="todo-wrap">
<!-- 头部 -->
<MyHeader @addTodo="addTodo"></MyHeader>
<!-- 主要内容区域 -->
<List :todoList="todoList"></List>
<!-- 页尾 -->
<MyFooter :todoList="todoList" @checkAll="checkAll" @deleteAllDone="deleteAllDone"></MyFooter>
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue';
import MyFooter from './components/MyFooter.vue';
import List from './components/List.vue';
export default {
name: 'App',
components: {
MyHeader, MyFooter, List
},
data() {
return {
todoList: JSON.parse(localStorage.getItem('todoList')) || []
}
},
methods: {
...
//更新todo的value
updateTodoEdit(id,title) {
this.todoList.forEach((todo) => {
if(todo.id === id) {
todo.title = title;
}
})
}
},
watch: {
todoList: {
deep:true,
handler(value) {
localStorage.setItem('todoList',JSON.stringify(this.todoList));
}
}
},
mounted() {
this.$bus.$on('checkTodo',this.checkTodo);
this.$bus.$on('deleteTodo',this.deleteTodo);
this.$bus.$on('updateTodoEdit',this.updateTodoEdit);
},
beforeDestroy() {
this.$bus.$off('checkTodo');
this.$bus.$off('deleteTodo');
this.$bus.$off('updateTodoEdit');
}
}
</script>