文章目录
一、浏览器本地存储
1. 相关API
-
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
-
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
-
相关API:
-
xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。 -
xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。
-
xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
-
xxxxxStorage.clear()
该方法会清空存储中的所有数据。
-
-
备注:
- SessionStorage存储的内容会随着浏览器窗口关闭而消失。
- LocalStorage存储的内容,存储在本地硬盘里,需要手动清除才会消失(clear或者手动清除)
xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是null。JSON.parse(null)
的结果依然是null。xxxStorage
里只能存储字符串,因此存储对象时,需借助JSON.stringfy()
将对象转化为字符串进行存储。读取时,需借助JSON.parse()
将读出来的字符串转为对象。
2. Todo案例中的应用
- 什么时候存:todo被增加、删除、修改时。
- 在哪里进行操作:如果是在todos的增删改的监听事件里进行存储,太麻烦。最好的方式是用watch,无论对todos进行任何的增删改查操作,只要todos发生变化,就将最新的todos重新存一份。
- 什么时候读取:data里数据初始化时。
// App.vue
data () {
return {
// 为什么要或一个空数组[]
todos: JSON.parse(localStorage.getItem('todos')) || []
}
},
watch: {
todos: {
deep: true, // 开启深度监视,监视到todos里某一个对象里面的属性的变化。
// 若不开启深度监视,只能检测到todos里每一个对象的增加或删除,eeee
handler (value) {
localStorage.setItem('todos', JSON.stringify(value))
}
}
},
(为什么要或空数组[]) 答: 最开始是没有数据的,此时读取数据为null,JSON转完对象后也为null,而footer中用到todos.length, null没有length这个属性,就会报错。所以没数据时,给个空数组。
二、组件的自定义事件
(click
、keyup
这些内置事件是给html元素用的。此处的自定义事件是给组件用的。)
通过子组件给父组件传值来举例自定义事件:
需求:(1) 发送学校名称给App组件,采用props的方式。(2)发送学生名称给App组件,采用自定义事件的方式
1. 回顾props传值方式
父组件向子组件传递函数,子组件调用这个函数,向父组件传值
父组件:App.vue
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据-->
<School :getSchoolName="getSchoolName"></School>
...
<script>
methods:{
getSchoolName (schoolName) {
console.log('App接收到了学校名称为:' + schoolName);
}
}
</script>
...
子组件:School.vue
<button @click="sendSchoolName">发送学校名称给APP</button>
<script>
...
props: ['getSchoolName'],
methods: {
sendSchoolName () {
// 调用父组件传递过来的方法
this.getSchoolName(this.name)
}
},
...
</script>
2. 绑定自定义事件
(1)方式一:v-on或@
<!-- 通过父组件给子组件绑定一个自定义事件实现(使用@/v-on):子给父传递数据-->
<Student @sendMsg="getStuName"></Student>
<!--如果只调用一次,once修饰符仍可用-->
<!-- <Student @sendMsg.once="getStuName"></Student> -->
<script>
...
getStuName (stuName,...a) {
console.log('App接收到了学生名称为:', stuName, a);
},
...
</script>
@(或v-on)在Student标签身上,所以给Student的组件实例对象vc身上绑定了一个名为sendMsg
的事件,当触发sendMsg
事件时,getStuName事件就会被调用。
接下里的问题是:如何触发sendMsg
事件?
由于是给Student组件的实例对象绑定的这个sendMsg
事件,所以触发也应该发生在Student组件的实例对象(vc)里。
触发事件:$emit('要触发的事件',参数1,参数2,...)
<!--Student组件-->
<button @click="sendStuName">发送学生名称给App</button>
<script>
...
sendStuName () {
// 通过this拿到Student组件的实例对象,
// 触发Student组件实例身上的sendMsg事件
this.$emit('sendMsg', this.name, 666, 888)
}
...
</script>
(2)方式二: ref
通过refs获取到组件实例对象
<!--App组件-->
<!-- 通过父组件给子组件绑定一个自定义事件实现(ref):子给父传递数据-->
<Student ref="student"></Student>
<script>
methods: {
getStuName (stuName,...a){
console.log('App接收到了学生名称为:', stuName, a);
}
},
// 生命周期函数
mounted () {
console.log('mounted')
// this.$refs.student 获取到student的组件实例对象
// 当sendMsg被触发时,执行回调函数getStuName
this.$refs.student.$on('sendMsg', this.getStuName)
}
</script>
可实现同样的效果。
需要注意的是,自定义事件的两种方式:@/v-on 与ref 指的是绑定自定义事件的两种方式。无论哪种绑定方式,Student组件里触发事件使用的都是$emit
。
ref的方式要更灵活一点,比如要求App组件挂载之后,等5s,再给Student组件实例绑定事件。而@/v-on的方式则实现不了。
3. 解绑自定义事件
哪个组件绑定了事件,就在哪个组件里解绑
语法:
组件实例.$off('事件名')
:解绑一个事件组件实例.$off(['事件名1','事件名2'])
:解绑两个事件组件实例.$off()
:解绑所有事件
<!--Student组件-->
<button @click="unbind">解绑</button>
...
<script>
unbind () {
this.$off('sendMsg') // 解绑一个事件
},
</script>
4. 注意点
1、当$destory()
时,组件实例被销毁,销毁后该组件实例的自定义事件全都不再奏效。
2、$refs回调函数
需求:将App组件将收到的学生姓名展示在页面上
解决:
<template>
<div class="app">
<h2>{{ msg }},学生姓名为:{{ stuName }}</h2>
</div>
</template>
通过this.$refs.xxx.$on('事件名',回调函数)
绑定自定义事件时,回调要么配置在methods中
methods:{
getStuName (stuName,...a){
console.log('App接收到了学生名称为:', stuName, a);
// 需要data里的数据接收一下
this.stuName = stuName
}
},
mounted () {
this.$refs.student.$on('sendMsg', this.getStuName)
}
要么用箭头函数
mounted () {
this.$refs.student.$on('sendMsg', (stuName,...a) => {
console.log('App接收到了学生名称为:', stuName, a);
// 此处的this指向App实例
this.stuName = stuName
})
}
如果不用箭头函数,this指向会出问题
mounted () {
this.$refs.student.$on('sendMsg', function (stuName,...a) {
console.log('App接收到了学生名称为:', stuName, a);
// 此处的this指的不是App组件,而是Student组件实例
this.stuName = stuName
})
}
3、组件上写的v-on都会被当成自定义事件,即便是写点击事件@click
也会当成自定义事件,想要用原生DOM事件的话,需要加native
<Student ref="student" @click.native="show"></Student>
<script>
show () {
console.log('组件采用原生点击事件');
}
</script>
总结
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
-
绑定自定义事件:
(1) 第一种方式,在父组件中:
<Demo @atguigu="test"/>
或<Demo v-on:atguigu="test"/>
(2)第二种方式,在父组件中:
<Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('atguigu',this.test) }
-
若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。 -
触发自定义事件:
this.$emit('atguigu',参数)
-
解绑自定义事件
this.$off('atguigu')
-
组件上也可以绑定原生DOM事件,需要使用
native
修饰符。 -
注意:通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
三、Todo案例采用自定义事件
此处只关注子组件向父组件传值的地方,即MyHeader与MyFooter向父组件App传值的地方
(MyList中:todos
传递的是数据,而不是函数,changeTodo
和deleteTodo
涉及到App与App孙子组件的事儿,此处不进行探讨)
App组件中:
<MyHeader @addTodo="addTodo"></MyHeader>
<MyFooter
:todos="todos"
@checkAllTodo="checkAllTodo"
@clearAllTodo="clearAllTodo"
></MyFooter>
MyHeader里:
// this.addTodo(todoObj)
this.$emit('addTodo', todoObj)
同理MyFooter中
clearAll () {
// this.clearAllTodo()
this.$emit('clearAllTodo')
}
isAll: {
get () {
return this.doneTotal === this.total && this.total > 0
},
set (value) {
// this.checkAllTodo(value)
this.$emit('checkAllTodo', value)
}
}