目录
TodoList案例(版本一)
App.vue
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<ListHeader :addTodo="addTodo"/>
</div>
<TodoList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></TodoList>
<ListFooter :todoList="todoList" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></ListFooter>
</div>
</div>
</div>
</template>
<script>
//引入组件
import ListHeader from './components/ListHeader'
import TodoList from './components/TodoList'
import ListFooter from './components/ListFooter'
export default {
name: "App",
components:{
ListHeader,
TodoList,
ListFooter
},
data(){
return{
todoList:[
{id:'001',title:'Vue',done:true},
{id:'002',title:'减肥',done:false},
{id:'003',title:'学习',done:true},
{id:'004',title:'刷抖音',done:true}
]
}
},
methods:{
//添加一个todo
addTodo(todoObj){
this.todoList.unshift(todoObj)
},
//勾选或取消勾选todo
checkTodo(id){
this.todoList.forEach((todo)=>{
if(todo.id === id) todo.done = !todo.done
})
},
//删除一个todo
deleteTodo(id){
this.todoList = this.todoList.filter(todo => todo.id !== id)
},
//全选或全不选
checkAllTodo(done){
this.todoList.forEach(todo=>{
todo.done = done
})
},
//清除所有已经完成的todo
clearAllTodo(){
this.todoList = this.todoList.filter(todo =>{
return !todo.done
})
}
}
}
</script>
ListHeader.vue
<template>
<div>
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name: "ListHeader",
props:['addTodo'],
data(){return {
title: ''
}},
methods: {
add(){
//校验数据
if (!this.title.trim()) return alert('输入不能为空')
//将用户的输入包装成一个todo对象
const todoObj ={id:nanoid(),title:this.title,done:false}
//通知App组件去添加一个todo对象
this.addTodo(todoObj)
//清空输入
this.title =''
}
},
}
</script>
TodoList.vue
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<ListHeader :addTodo="addTodo"/>
</div>
<TodoList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></TodoList>
<ListFooter :todoList="todoList" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></ListFooter>
</div>
</div>
</div>
</template>
<script>
//引入组件
import ListHeader from './components/ListHeader'
import TodoList from './components/TodoList'
import ListFooter from './components/ListFooter'
export default {
name: "App",
components:{
ListHeader,
TodoList,
ListFooter
},
data(){
return{
todoList:[
{id:'001',title:'Vue',done:true},
{id:'002',title:'减肥',done:false},
{id:'003',title:'学习',done:true},
{id:'004',title:'刷抖音',done:true}
]
}
},
methods:{
//添加一个todo
addTodo(todoObj){
this.todoList.unshift(todoObj)
},
//勾选或取消勾选todo
checkTodo(id){
this.todoList.forEach((todo)=>{
if(todo.id === id) todo.done = !todo.done
})
},
//删除一个todo
deleteTodo(id){
this.todoList = this.todoList.filter(todo => todo.id !== id)
},
//全选或全不选
checkAllTodo(done){
this.todoList.forEach(todo=>{
todo.done = done
})
},
//清除所有已经完成的todo
clearAllTodo(){
this.todoList = this.todoList.filter(todo =>{
return !todo.done
})
}
}
}
</script>
TodoItem.vue
<template>
<li>
<label>
<!-- 也可绑定change事件-->
<input type="checkbox" :checked="todoObj.done" @click="handleCheck(todoObj.id)"/>
<span>{{todoObj.title}}</span>
</label>
<button class="btn btn-danger" @click="deleteList(todoObj.id)">删除</button>
</li>
</template>
<script>
export default {
name: 'TodoItem',
props: ['todoObj','checkTodo','deleteTodo'],
methods:{
//勾选或取消勾选
handleCheck(id){
//通知App将对应的todo对象done值取反
this.checkTodo(id)
},
//删除
deleteList(id){
if(confirm('确定删除吗?')){
//通知App将对应的todo对象done值删除
this.deleteTodo(id)
}
}
}
}
</script>
ListFooter.vue
<template>
<div class="todo-footer" v-show="total">
<label>
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/>-->
<input type="checkbox" v-model="isAll"/>
</label>
<span>
<span>已完成{{doneTotal}}</span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "ListFooter",
props:['todoList','checkAllTodo','clearAllTodo'],
computed:{
total(){
return this.todoList.length
},
doneTotal(){
// let i =0
// this.todoList.forEach((todo)=>{
// if(todo.done) i++
// })
// return i
return this.todoList.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
},
isAll:{
get(){
return this.doneTotal === this.total && this.total > 0
},
set(checked){
this.checkAllTodo(checked)
}
}
},
methods:{
clearAll(){
this.clearAllTodo()
}
// checkAll(event){
// this.checkAllTodo(event.target.checked)
// }
}
}
</script>
备注:安装nanoid:npm i nanoid
用于生成独一无二的id
效果
总结TodoList案例
1、组件化编码流程:
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
- 一个组件在用:放在组件自身即可。
- 一些组件在用:放在他们共同的父组件上(
<span style="color:red">
状态提升</span>
)。
(3)实现交互:从绑定事件开始。
2、props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
3、使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4、props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
webStorage
- 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
- 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
- 相关API:
(1)xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
(2)xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。
(3)xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
(4)xxxxxStorage.clear()
该方法会清空存储中的所有数据。 - 备注:
(1)SessionStorage存储的内容会随着浏览器窗口关闭而消失。
(2) LocalStorage存储的内容,需要手动清除才会消失。
(3)xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是null。
(4)JSON.parse(null)
的结果依然是null。
TodoList的本地存储
App.vue
data(){
return{
todoList:JSON.parse(localStorage.getItem('todos')) || []
}
},
···
watch:{
todoList:{
//默认开启的不是深度监视
deep:true,
handler(value){
localStorage.setItem('todos',JSON.stringify(value))
}
}
}
组件的自定义事件
- 一种组件间通信的方式,适用于:
<strong style="color:red">
子组件 ===> 父组件</strong>
- 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(
<span style="color:red">
事件的回调在A中</span>
)。 - 绑定自定义事件:
(1) 第一种方式,在父组件中:<Demo @atguigu="test"/>
或<Demo v-on:atguigu="test"/>
(2)第二种方式,在父组件中:
js <Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('atguigu',this.test) }
(3)若想让自定义事件只能触发一次,可以使用once
修饰符,或$once
方法。 - 触发自定义事件:
this.$emit('atguigu',数据)
- 解绑自定义事件
this.$off('atguigu')
- 组件上也可以绑定原生DOM事件,需要使用
native
修饰符。 - 注意:通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
绑定
App.vue
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据-->
<SchoolImformation :getSchoolName="getSchoolName"></SchoolImformation>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 (第一种写法,使用@或v-on) 如果只触发一次将on换成once-->
<!-- <StudentInformatin v-on:xw="getStudentName"></StudentInformatin>-->
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 (第二种写法,使用ref)-->
<StudentInformatin ref="studentInformatin"></StudentInformatin>
···
methods:{
getSchoolName(name){
console.log('收到了学校名:',name)
},
//将其他信息打包到params中
getStudentName(name,...params){
console.log('收到了姓名:',name,params)
}
},
mounted(){
//不需要添加定时器等
// this.$refs.studentInformatin.$on('xw',this.getStudentName)
setTimeout(()=>{
//如果只触发一次,将on换成once
this.$refs.studentInformatin.$on('xw',this.getStudentName)
},3000)
}
}
StudentInformatin.vue
<button @click="sendStudentName">把姓名给App</button>
···
methods:{
sendStudentName(){
//触发StudentInformatin的实例对象身上的xw事件
this.$emit('xw',this.name,this.age,this.msg)
}
}
解绑
<button @click="unbind">解绑xw事件</button>
···
unbind(){
//解绑一个自定义事件
// this.$off('xw')
//解绑多个自定义事件
// this.$off(['xw','demo'])
//解绑所有的自定义事件
this.$off()
}
TodoList使用组件的自定义事件
App.vue
<ListHeader @addTodo="addTodo"/>
ListHeader.vue
this.$emit('addTodo',todoObj)