组件自定义事件
1. 一种组件间通信的方式,适用于:子组件
===> 父组件
2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中
)。
3. 绑定自定义事件:
- 第一种方式,在父组件中:
<Demo @atguigu="test"/>
或 <Demo v-on:atguigu="test"/>
- 第二种方式,在父组件中:
1 2 3 4 5
| <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指向会出问题!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <h2>当前求和为:{{number}}</h2> <button @click="add">点我number++</button> <button @click="sendStudentlName">把学生名给App</button> <button @click="unbind">解绑atguigu事件</button> <button @click="death">销毁当前Student组件的实例(vc)</button> </div> </template>
<script> export default { name:'Student', data() { return { name:'张三', sex:'男', number:0 } }, methods: { add(){ console.log('add回调被调用了') this.number++ }, sendStudentlName(){ this.$emit('atguigu',this.name,666,888,900) }, unbind(){ this.$off('atguigu') }, death(){ this.$destroy() } }, } </script>
<style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <div class="school"> <h2>学校名称:{{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> .school{ background-color: skyblue; padding: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <template> <div class="app"> <h1>{{msg}},学生姓名是:{{studentName}}</h1>
<School :getSchoolName="getSchoolName"/>
<Student ref="student" @click.native="show"/> </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: { getSchoolName(name){ console.log('App收到了学校名:',name) }, getStudentName(name,...params){ console.log('App收到了学生名:',name,params) this.studentName = name }, m1(){ console.log('demo事件被触发了!') }, show(){ alert(123) } }, mounted() { this.$refs.student.$on('atguigu',this.getStudentName) }, } </script>
<style scoped> .app{ background-color: gray; padding: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({ el:'#app', render: h => h(App),
})
|
TodoList自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| <template> <div class="todo-footer" v-show="total"> <label> <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:'MyFooter', props:['todos'], computed: { total(){ return this.todos.length }, doneTotal(){
return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0) }, isAll:{ get(){ return this.doneTotal === this.total && this.total > 0 }, set(value){ this.$emit('checkAllTodo',value) } } }, methods: {
clearAll(){ this.$emit('clearAllTodo') } }, } </script>
<style scoped> .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; }
.todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; }
.todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; }
.todo-footer button { float: right; margin-top: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <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', data() { return { title:'' } }, methods: { add(){ if(!this.title.trim()) return alert('输入不能为空') const todoObj = {id:nanoid(),title:this.title,done:false} this.$emit('addTodo',todoObj,1,2,3) this.title = '' } }, } </script>
<style scoped> .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; }
.todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| <template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> </li> </template>
<script> export default { name:'MyItem', props:['todo','checkTodo','deleteTodo'], methods: { handleCheck(id){ this.checkTodo(id) }, handleDelete(id){ if(confirm('确定删除吗?')){ this.deleteTodo(id) } } }, } </script>
<style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; }
li label { float: left; cursor: pointer; }
li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; }
li button { float: right; display: none; margin-top: 3px; }
li:before { content: initial; }
li:last-child { border-bottom: none; }
li:hover{ background-color: #ddd; } li:hover button{ display: block; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <template> <ul class="todo-main"> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo" /> </ul> </template>
<script> import MyItem from './MyItem'
export default { name:'MyList', components:{MyItem}, props:['todos','checkTodo','deleteTodo'] } </script>
<style scoped> .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; }
.todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader @addTodo="addTodo"/> <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> </div> </div> </div> </template>
<script> import MyHeader from './components/MyHeader' import MyList from './components/MyList' import MyFooter from './components/MyFooter.vue'
export default { name:'App', components:{MyHeader,MyList,MyFooter}, data() { return { todos:JSON.parse(localStorage.getItem('todos')) || [] } }, methods: { addTodo(todoObj){ this.todos.unshift(todoObj) }, checkTodo(id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, deleteTodo(id){ this.todos = this.todos.filter( todo => todo.id !== id ) }, checkAllTodo(done){ this.todos.forEach((todo)=>{ todo.done = done }) }, clearAllTodo(){ this.todos = this.todos.filter((todo)=>{ return !todo.done }) } }, watch: { todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } }, } </script>
<style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12
| import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({ el:'#app', render: h => h(App) })
|
全局事件总线
1. 一种组件间通信的方式,适用于任意组件间通信。
2. 安装全局事件总线:
1 2 3 4 5 6 7
| new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this }, ...... })
|
3. 使用事件总线:
- 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
1 2 3 4 5 6 7
| methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) }
|
- 提供数据:
this.$bus.$emit('xxxx',数据)
- 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template>
<script> export default { name:'School', data() { return { name:'尚硅谷', address:'北京', } }, mounted() { this.$bus.$on('hello',(data)=>{ console.log('我是School组件,收到了数据',data) }) }, beforeDestroy() { this.$bus.$off('hello') }, } </script>
<style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template>
<script> export default { name:'Student', data() { return { name:'张三', sex:'男', } }, mounted() { }, methods: { sendStudentName(){ this.$bus.$emit('hello',this.name) } }, } </script>
<style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> <div class="app"> <h1>{{msg}}</h1> <School/> <Student/> </div> </template>
<script> import Student from './components/Student' import School from './components/School'
export default { name:'App', components:{School,Student}, data() { return { msg:'你好啊!', } } } </script>
<style scoped> .app{ background-color: gray; padding: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({ el:'#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this }, })
|
TodoList之全局事件总线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| <template> <div class="todo-footer" v-show="total"> <label> <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:'MyFooter', props:['todos'], computed: { total(){ return this.todos.length }, doneTotal(){
return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0) }, isAll:{ get(){ return this.doneTotal === this.total && this.total > 0 }, set(value){ this.$emit('checkAllTodo',value) } } }, methods: {
clearAll(){ this.$emit('clearAllTodo') } }, } </script>
<style scoped> .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; }
.todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; }
.todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; }
.todo-footer button { float: right; margin-top: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <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', data() { return { title:'' } }, methods: { add(){ if(!this.title.trim()) return alert('输入不能为空') const todoObj = {id:nanoid(),title:this.title,done:false} this.$emit('addTodo',todoObj,1,2,3) this.title = '' } }, } </script>
<style scoped> .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; }
.todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| <template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> </li> </template>
<script> export default { name:'MyItem', props:['todo'], methods: { handleCheck(id){ this.$bus.$emit('checkTodo',id) }, handleDelete(id){ if(confirm('确定删除吗?')){ this.$bus.$emit('deleteTodo',id) } } }, } </script>
<style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; }
li label { float: left; cursor: pointer; }
li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; }
li button { float: right; display: none; margin-top: 3px; }
li:before { content: initial; }
li:last-child { border-bottom: none; }
li:hover{ background-color: #ddd; } li:hover button{ display: block; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <template> <ul class="todo-main"> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" /> </ul> </template>
<script> import MyItem from './MyItem'
export default { name:'MyList', components:{MyItem}, props:['todos'] } </script>
<style scoped> .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; }
.todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader @addTodo="addTodo"/> <MyList :todos="todos"/> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> </div> </div> </div> </template>
<script> import MyHeader from './components/MyHeader' import MyList from './components/MyList' import MyFooter from './components/MyFooter.vue'
export default { name:'App', components:{MyHeader,MyList,MyFooter}, data() { return { todos:JSON.parse(localStorage.getItem('todos')) || [] } }, methods: { addTodo(todoObj){ this.todos.unshift(todoObj) }, checkTodo(id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, deleteTodo(id){ this.todos = this.todos.filter( todo => todo.id !== id ) }, checkAllTodo(done){ this.todos.forEach((todo)=>{ todo.done = done }) }, clearAllTodo(){ this.todos = this.todos.filter((todo)=>{ return !todo.done }) } }, watch: { todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } }, mounted() { this.$bus.$on('checkTodo',this.checkTodo) this.$bus.$on('deleteTodo',this.deleteTodo) }, beforeDestroy() { this.$bus.$off('checkTodo') this.$bus.$off('deleteTodo') }, } </script>
<style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({ el:'#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this }, })
|
消息订阅与发布(pubsub)
1. 一种组件间通信的方式,适用于任意组件间通信。
2. 使用步骤:
- 安装pubsub:
npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
- 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
1 2 3 4 5 6 7
| methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) }
|
- 提供数据:
pubsub.publish('xxx',数据)
- 最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template>
<script> import pubsub from 'pubsub-js' export default { name:'School', data() { return { name:'尚硅谷', address:'北京', } }, mounted() {
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{ console.log(this) }) }, beforeDestroy() { pubsub.unsubscribe(this.pubId) }, } </script>
<style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template>
<script> import pubsub from 'pubsub-js' export default { name:'Student', data() { return { name:'张三', sex:'男', } }, mounted() { }, methods: { sendStudentName(){ pubsub.publish('hello',666) } }, } </script>
<style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> <div class="app"> <h1>{{msg}}</h1> <School/> <Student/> </div> </template>
<script> import Student from './components/Student' import School from './components/School'
export default { name:'App', components:{School,Student}, data() { return { msg:'你好啊!', } } } </script>
<style scoped> .app{ background-color: gray; padding: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12
| import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({ el:'#app', render: h => h(App), })
|
TodoList之pubsub
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| <template> <div class="todo-footer" v-show="total"> <label> <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:'MyFooter', props:['todos'], computed: { total(){ return this.todos.length }, doneTotal(){
return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0) }, isAll:{ get(){ return this.doneTotal === this.total && this.total > 0 }, set(value){ this.$emit('checkAllTodo',value) } } }, methods: {
clearAll(){ this.$emit('clearAllTodo') } }, } </script>
<style scoped> .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; }
.todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; }
.todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; }
.todo-footer button { float: right; margin-top: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <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', data() { return { title:'' } }, methods: { add(){ if(!this.title.trim()) return alert('输入不能为空') const todoObj = {id:nanoid(),title:this.title,done:false} this.$emit('addTodo',todoObj,1,2,3) this.title = '' } }, } </script>
<style scoped> .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; }
.todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> </li> </template>
<script> import pubsub from 'pubsub-js' export default { name:'MyItem', props:['todo'], methods: { handleCheck(id){ this.$bus.$emit('checkTodo',id) }, handleDelete(id){ if(confirm('确定删除吗?')){ pubsub.publish('deleteTodo',id) } } }, } </script>
<style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; }
li label { float: left; cursor: pointer; }
li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; }
li button { float: right; display: none; margin-top: 3px; }
li:before { content: initial; }
li:last-child { border-bottom: none; }
li:hover{ background-color: #ddd; } li:hover button{ display: block; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <template> <ul class="todo-main"> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" /> </ul> </template>
<script> import MyItem from './MyItem'
export default { name:'MyList', components:{MyItem}, props:['todos'] } </script>
<style scoped> .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; }
.todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader @addTodo="addTodo"/> <MyList :todos="todos"/> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> </div> </div> </div> </template>
<script> import pubsub from 'pubsub-js' import MyHeader from './components/MyHeader' import MyList from './components/MyList' import MyFooter from './components/MyFooter'
export default { name:'App', components:{MyHeader,MyList,MyFooter}, data() { return { todos:JSON.parse(localStorage.getItem('todos')) || [] } }, methods: { addTodo(todoObj){ this.todos.unshift(todoObj) }, checkTodo(id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, deleteTodo(_,id){ this.todos = this.todos.filter( todo => todo.id !== id ) }, checkAllTodo(done){ this.todos.forEach((todo)=>{ todo.done = done }) }, clearAllTodo(){ this.todos = this.todos.filter((todo)=>{ return !todo.done }) } }, watch: { todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } }, mounted() { this.$bus.$on('checkTodo',this.checkTodo) this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo) }, beforeDestroy() { this.$bus.$off('checkTodo') pubsub.unsubscribe(this.pubId) }, } </script>
<style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({ el:'#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this }, })
|
$nextTick
1. 语法:this.$nextTick(回调函数)
2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| <template> <li> <label> <input type="checkbox" :checked="todo.done" @change="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="inputTitle" > </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button> </li> </template>
<script> import pubsub from 'pubsub-js' export default { name:'MyItem', props:['todo'], methods: { handleCheck(id){ this.$bus.$emit('checkTodo',id) }, handleDelete(id){ if(confirm('确定删除吗?')){ pubsub.publish('deleteTodo',id) } }, handleEdit(todo){ if(todo.hasOwnProperty('isEdit')){ todo.isEdit = true }else{ this.$set(todo,'isEdit',true) } this.$nextTick(function(){ this.$refs.inputTitle.focus() }) }, handleBlur(todo,e){ todo.isEdit = false if(!e.target.value.trim()) return alert('输入不能为空!') this.$bus.$emit('updateTodo',todo.id,e.target.value) } }, } </script>
<style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; }
li label { float: left; cursor: pointer; }
li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; }
li button { float: right; display: none; margin-top: 3px; }
li:before { content: initial; }
li:last-child { border-bottom: none; }
li:hover{ background-color: #ddd; } li:hover button{ display: block; } </style>
|