一 TodoList案例分析
TodoList案例:App.vue
<template> <div> <div class="todo-container"> <!-- <my-header :addTodo="addTodo"/> --> <MyHeader @addTodo="addTodo"/> <List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo" /> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> </div> </div> </template> <script> import MyFooter from './components/MyFooter' import MyHeader from './components/MyHeader' import List from './components/List' import { clearLine } from 'readline'; export default { name: 'App', components: { MyFooter,MyHeader,List }, data() { return{ todos:JSON.parse(localStorage.getItem('todos'))||[] // todos:[ // {id:'001',title:'抽烟',done:true}, // {id:'002',title:'喝酒',done:false}, // {id:'003',title:'开车',done:true}, // ] } }, methods:{ addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选或取消勾选 checkTodo(id){ this.todos.forEach((todo)=> { if(todo.id==id)todo.done=!todo.done }) }, //删除一个todo deleteTodo(id){ this.todos=this.todos.filter(todo=>todo.id!==id) }, //全选或取消全选 checkAllTodo(done){ this.todos.forEach((todo)=>{ todo.done=done }) }, //清除所有已经完成的todo clearAllTodo(){ this.todos=this.todos.filter((todo)=>{ return !todo.done } ) } }, watch:{ //开启深度监视,对对象中的某个属性的变化开始监视 deep:true, handle(value){ localStorage.setItem('todos',JSON.stringify(value)) } }, } </script>
MyHeader.vue:
<template> <div> <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:'' } }, // 用下面这写法还要在input框加v-model="title" // data(){ // return{ // title:'' // } // }, // methods:{ // add(){ // console.log(this.title) // } // } methods:{ add(){ //校验数据是否为空 if(!this.title.trim())return alert('输入不能为空') //将用户的输入包装成一个todos对象 const todoObj={ id:nanoid(), title:this.title, done:false}, this.addTodo(todoObj) //通知App组件去添加一个todo对象 this.$emit('addTodo',todoObj) //清空输入 this.title='' // e.target.value='' } }, } </script>
List.vue:
<template> <ul class="todo-main"> <Item v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo"/> </ul> </template> <script> import Item from'./Item' export default { name:'List', components:{Item}, props:['todos','checkTodo','deleteTodo'] } </script>
Item.vue:
<template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> <!--如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> <!-- <input type=" checkbox" v- model=" todo . done"/>--> <span>{{todo.title}}</span> </label> <button @click="handleDelete(todo.id)">删除</button> </li> </template> <script> export default { name:'Item', //声明接收todo对象 props:['todo','checkTodo','deleteTodo'], methods:{ handleCheck(id){ //通过App组件将对应todo对象的done取反 this.checkTodo(id) }, handleDelete(id){ if(confirm('确定删除吗?')) { this.deleteTodo(id) } } } } </script> <style> li button{ display:none; margin-top:3px; } li :hover{ background-color: aqua } li:hover button{ display:block } </style>
MyFooter.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="clickAll">清除已完成任务</button> </div> </template> <script> export default { name:'MyFooter', // props:['todos','checkAllTodo','clearAllTodo'], props:['todos'], computed:{ doneTotal(){ /* const x = this.todos.reduce( (pre, current )=>{ console.1og( ' @' , pre, current) return pre + (current.done ? 1 : 0) },0) */ return this.todos.reduce((pre,current)=>pre+(current.done?1:0),0) }, total(){ return this.totals.length }, //勾还是不勾 isAll:{ get(){ return this.doneTotal===this.total &&this.total>0 }, set(value){ // this.checkAllTodo(value) this.$emit('checkAllTodo',value) } } }, methods:{ /* checkAll(e){ //借助dom拿到一个属性 this.checkAllTodo(e.target.checked) }*/ clickAll(){ // this.clearAllTodo() this.$emit('clickAllTodo') } } } </script>
分析:
1.组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用, 还是一
些组件在用:
1).一个组件在用:放在组件自身即可.
2). -些组件在用:放在他们共同的父组件上(状态提升).
(3).实现交互:从绑定事件开始。
2. props适用于:
(1).父组件==>子组件通信
(2).子组件==>父组件通信(要求父先给子-个函数)
3.使用v-modeI时要切记: v-model绑定的值不能是props传过来的值,因为props
是不可以修改的!
4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推
荐这样做。
二 浏览器本地存储
1.存储内容大小- -般支持5MB左右(不同浏览器可能还不一-样)
2.浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制。
3.相关API:
1. xxxxStorage. setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
2. xxxxxStorage . getItem('person');
该方法接受-个键名作为参数, 返回键名对应的值。
3. xxxxxStorage . removeItem('key');
该方法接受-个键名作为参数,并把该键名从存储中删除。
4. xxxxStorage.clear()
该方法会清空存储中的所有数据。
4.备注:
1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
2. LocalStorage存储的内容,需要手动清除才会消失。
3. xxxxStorage . getItemxxx)如果xxx对应的value获取不到,那么getltem的返回值是null.
4.JSON. parse(null)的结果依然是null.,
三 组件的自定义事件
1.一种组件间通信的方式,适用于:子组件===>父组件
2.使用场景: A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
3.绑定自定义事件:
1.第一种方式,在父组件中: <Demo @heihei="test"/> 或<Demo v-on:heihei="test"/>
2.第二种方式,在父组件中:
<Demo ref="demo"/>
......
mounted(){
this. $refs . xxx. $on(' heihei' ,this.test)
}
3.若想让自定义事件只能触发一次, 可以使用once修饰符,或$once方法。
4.触发自定义事件: this . $emit('heihei' ,数据)
5.解绑自定义事件this. $off( ' heihei' )
6.组件上也可以绑定原生DOM事件,需要使用native修饰符。
7.注意:通过this. $refs. xxx. $on( 'hiehei',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
如下例:App组件:
<template> <div class="app"> <h1>{{msg}}我是{{studentName}}</h1> <!-- 通过父组件给子组件传递函数类型的props实现子给父传递数据 --> <School :getSchoolName="getSchoolName"/> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on实现) --> <!-- 只触发一次时 <Student @heihei.once="getStudentName"/>--> <Student v-on:heihei="getStudentName" @de="abc"/> <!--通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) <Student ref="student" /> --> <!-- 原生dom事件绑定在组件上会被认为是自定义事件 : <Student ref="student" @click="show" /> --> <!-- <Student ref="student" @click.native="show" /> 才可以被解析为原生dom事件--> </div> </template> <script> import Student from'./components/Student' import School from'./components/School' export default ({ name:'App', components:{School,Student}, data(){ return{ msg:'嘿嘿', studentName:'' } }, methods:{ show(){alert('123')}, getSchoolName(name){ console.log('APP收到学校名',name) }, //事件的回调 getStudentName(name,...params){ console.log('APP收到学生名',name,params ) this.StudnetName=name }, abc(){ console.log('哈哈,我被触发了') } }, mounted(){ //给Studnet组件绑定事件,事件的回调:this.getStudentName // this.$refs.student.$on('heihei',this.getStudentName) // 只触发一次事件 // this.$refs.student.$once('heihei',this.getStudentName) //(普通函数谁调用谁是this, heihei事件是student实例调用的,所以回调函数this是student组件实例对象, /* this.$refs.student.$on('heihei',function(name,...params){ console.log('APP收到学生名',name,params ) console.log(this)//this是触发heihei事件的,即student组件实例对象 //不行:this.StudnetName=name */ /* // 箭头函数this指向声明函数时的作用域下,此时this是app组件实例对象 this.$refs.student.$on('heihei',(name,...params)=>{ console.log('APP收到学生名',name,params) this.studentName=name }) */ }}) </script> <style> .app{ background-color:red } </style>
Studnet组件:
<template> <div class="demo"> <h2 class="title">学生姓名:{{name}}</h2> <h2 class="qw">学生性别:{{sex}}</h2> <h2>{{number}}</h2> <button @click="add">点我number++</button> <button @click="sendStudentName">把学生名给App</button> <button @click="unbind">解绑事件</button> <button @click="death">点我销毁Student事件</button> </div> </template> <script> export default{ name:'Student', data(){ return{ name:'张三', sex:'男', number:0 } }, methods:{ //vm销毁对原生事件不影响 add(){ this.number++ }, sendStudentName(){ //触发Student组件实例身上的heihei事件 this.$emit('heihei',this.name,600,7000,8000) // this.$emit('de') // this.$emit('click') } , unbind(){ // 解绑一个自定义事件: this.$off('heihei') //解绑多个自定义事件:this.$off(['heihei','abc']) //解绑所有自定义事件 this.$off() }, death(){ //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。 this.$destroy() } } } </script> <style scoped> .demo{ background-color: aqua } </style>
School组件:
<template> <div class="demo"> <h2 class="title">学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="sendSchoolName">把学校名给App</button> </div> </template> <script> export default{ name:'School', props:['getSchoolName'], data(){ return{ name:'某学校', address:'某地' } }, methods:{ sendSchoolName(){ this.getSchoolName(this.name) } } } </script> <style scoped> /* scoped样式 作用:让样式在局部生效,防止冲突。 */ .demo{ background-color: yellow; } </style>
四 全局事件总线
1 定义:一种组件间通信的方式,适用于任意组件间通信。
2.安装全局事件总线:
new Vue({
beforeCreate() {
Vue . prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
})
3.使用事件总线:
1.接收数据: A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){......}
}
mounted() {
this . $bus . $on( ' xxx' ,this . demo)
}
I_
2.提供数据: this.$bus . $emit( 'xxx' ,数据)
4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。如要想把学生名给School组件,
School组件(主要代码)
Student组件