🥔:高度自律即自由
更多Vue知識請點擊——Vue.js
VUE2-Day9
- 全局事件總線
- 1、安裝全局事件總線
- 2、使用事件總線
- (1)接收數據
- (2)提供數據
- (3)組件銷毀前最好解綁
- 3、TodoList中的孫傳父
- (1)首先在main.js中安裝全局事件總線
- (2)在App.vue中綁定全局自定義事件,并使用之前寫好的回調
- (3)Item中觸發事件,并把數據id傳過去
- 消息的訂閱與發布
- (1)使用消息的訂閱與發布
- (2)TodoList案例使用消息的訂閱與發布
- TodoList的編輯功能
- 1、整體思路
- 2、給標簽添加事件
- 3、點擊編輯按鈕切換span為input
- 4、失去焦點傳數據
- 5、App收數據
- $nextTick
- 動畫與過渡
- 進入離開動畫三種寫法
全局事件總線
一種組件間通信的方式,適用于任意組件間通信。通俗理解就是一個定義在所有組件之外的公共嘎達,這個嘎達可以有vm或vc上的同款$on、$off、$emit
,也可以讓所有組件都訪問到。要想實現這個事情,只能在Vue.prototype
上添加一個屬性,值是vm或vc
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-q3OYNFD4-1692378989491)(D:\wxf\前端學習筆記\vue2+vue3\筆記圖片\全局事件總線.png)]
vm.$emit( event, arg ) //觸發當前實例上的事件,arg是傳遞給父組件的參數
vm.$on( event, fn ) //監聽event事件后運行 fn
$off(type, fn) //注銷消息方法 type:消息名稱 fn:消息回調函數
1、安裝全局事件總線
安裝全局事件總線可以用vc也可以用vm,寫在main.js里面
- 用vc的話這么寫:
const Demo = Vue.extend({});
const d = new Demo();
Vue.prototype.$bus = d;
- 用vm的話這么寫(我們通常用vm):
new Vue({el: '#app',render: (h) => h(App),//放這個函數里,是因為模板還未解析,這個函數在自定義事件定義之前,是不會報錯滴beforeCreate() {Vue.prototype.$bus = this //安裝全局事件總線},
})
在這里我們使用vm安裝全局事件總線。
2、使用事件總線
(1)接收數據
接收數據:A組件想接收數據,則在A組件中給$bus
綁定自定義事件,事件的回調留在A組件自身。
methods(){demo(data){......}
}
......
mounted() {this.$bus.$on('xxxx',this.demo)
}
(2)提供數據
任意一個組件,都可以給上面說的A組件傳數據
methods:{sendStudentName(){this.$bus.$emit('xxxx',this.name)}
}
(3)組件銷毀前最好解綁
最好在beforeDestroy鉤子中,用`$of去解綁當前組件所用到的事件。
因為接收數據的組件A中定義的回調函數和自定義事件是綁定的,而這個用來接收數據的組件實例A都銷毀了,回調函數也沒了,那這個自定義事件也就沒用了,你留著會污染全局環境。
beforeDestroy() {this.$bus.$off('hello')
}
3、TodoList中的孫傳父
之前我們孫傳父都是父親傳給兒子函數,兒子傳給孫子函數,然后孫子再調用函數傳值,很麻煩,但是現在我們可以用全局事件總線實現孫子給父親傳數據。此處只展示修改部分,記得把之前方法的相關代碼刪掉或注釋掉。
(1)首先在main.js中安裝全局事件總線
new Vue({el: '#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus = this; //創建全局事件總線}
});
(2)在App.vue中綁定全局自定義事件,并使用之前寫好的回調
mounted() {//掛載完成后給全局事件總線添加事件this.$bus.$on('changeTodo', this.changeTodo);this.$bus.$on('deleteTodo', this.deleteTodo);
},
beforeDestroy() {//最好在銷毀前解綁this.$bus.$off(['changeTodo', 'deleteTodo']);
},
(3)Item中觸發事件,并把數據id傳過去
handleChange(id) {//觸發全局事件總線中的事件this.$bus.$emit('changeTodo', id);
},
handleDelete(id) {if (confirm('確定要刪除嗎?')) //點確定是true,取消是falsethis.$bus.$emit('deleteTodo', id);
}
消息的訂閱與發布
(1)使用消息的訂閱與發布
消息的訂閱與發布用的不多,和全局事件總線寫法差不多,但是全局事件總線更好,因為是在Vue身上操作,但是這個的話要引入第三方庫,庫有很多,比如pubsub-js
安裝pubsub:npm i pubsub-js
引入:import pubsub from 'pubsub-js
接收數據:A組件想接收數據,則在A組件中訂閱消息,訂閱的回調留在A組件自身。
接收兩個參數,第一個是消息名字,第二個是傳過來的數據
methods(){demo(msgName,data){......}
}
......
mounted() {//訂閱消息this.pubsubId = pubsub.subscribe('xxx',this.demo)
},
beforeDestroy() {//銷毀時取消訂閱pubsub.unsubscribe(this.pubsubId);
},
提供數據:
methods: {sendStudentName() {// this.$bus.$emit('hello', this.name);//發布消息并傳數據pubsub.publish('hello', this.name); }
},
可以對比一下前面的全局事件總線寫法,個人認為寫法差別不大。
可以嘗試把TodoList案例里孫傳父使用全局事件總線的寫法改成使用消息訂閱與發布試試。
(2)TodoList案例使用消息的訂閱與發布
這里把**deleteTodo
改成消息訂閱與發布,changeTodo
依舊使用全局事件總線**,大家可以仔細對比他們的區別。
- App.vue(訂閱消息/接收數據):
mounted() {//掛載完成后給全局事件總線添加事件this.$bus.$on('changeTodo', this.changeTodo)// this.$bus.$on('deleteTodo', this.deleteTodo)//deleteTodo換一種方法 用訂閱消息寫this.pubsubIs = pubsub.subscribe('deleteTodo', this.deleteTodo)},beforeDestroy() {//最好在銷毀前解綁// this.$bus.$off(['changeTodo', 'deleteTodo'])//使用全局事件總線解綁this.$bus.$off('changeTodo')//deleteTodo換一種方法 用訂閱消息解綁pubsub.unsubscribe(this.pubsubId)},
這里別忘了這個subscribe里的回調,接收兩個參數,第一個是消息名,后面才是數據,所以deleTodo方法得加個參數
//雖然msgName沒用上,但是如果不加就會把id當第一個參數,id就變成msgName,所以這里必須要加,或者直接拿一個下劃線_占位也行
deleteTodo(msgName, id) {this.todos = this.todos.filter(todo => todo.id !== id);},
- Item.vue(發布消息/提供數據)
handleChange(id) {//使用全局事件總線//觸發全局事件總線中的事件this.$bus.$emit('changeTodo', id);
},
handleDelete(id) {//confirm點確定是true,取消是falseif (confirm('確定要刪除嗎?')) //使用消息訂閱與發布發布消息pubsub.publish('deleteTodo', id); //發布消息
}
TodoList的編輯功能
1、整體思路
首先得有一個按鈕,點擊之后出現input框,框里是todo.title,而且原來的span要隱藏。然后修改完之后,失去焦點會自動更新數據,并且span出現,input框隱藏。
除此之外還有個細節,那就是點擊編輯要自動獲取焦點,要不然會有一個小問題(點擊編輯,然后突然不想改了,還得手動點一下input,再點下別的地方,才會變回span)
想實現span和input的來回切換,就要給todo添加新的屬性,用來標識這個變換,這里起名叫isEdit
所以大致思路:給標簽添加事件 => 點擊編輯按鈕切換span為input => 失去焦點傳數據 => App收數據 => 解決焦點bug
2、給標簽添加事件
(1)isEdit一上來是沒有的,所以todo.isEdit = false,再加上默認上來顯示的是span,所以span加個v-show=“!todo.isEdit”,input加個v-show=“todo.isEdit” ,button加v-show="!todo.isEdit"是因為我們一般編輯時,這個按鈕應該消失才對,所以和span一致
(2)由于props接過來的數據不能改,所以使用單向數據綁定:value=“todo.title”
(3)ref=“inputTitle” 是為了方便后面拿到input元素然后操作它寫的(nextTick)
(4)@blur="handleBlur(todo, $event)"是失去焦點時觸發的事件,用來給App傳值
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<input
type="text"
v-show="todo.isEdit"
:value="todo.title"
ref="inputTitle"
@blur="handleBlur(todo, $event)"><button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">編輯</button>
給這個編輯按鈕加個樣式:
.btn-edit {color: #fff;background-color: rgb(13, 166, 13);border: 1px solid green;margin-right: 5px;
}.btn-edit:hover {color: #fff;background-color: green;
}
3、點擊編輯按鈕切換span為input
點擊編輯給todo追加屬性,用來切換span為input。這里考慮到給todo追加屬性的問題,如果想要讓Vue監測到這個屬性,那么必須使用$set來添加isEdit,且默認值為true(因為編輯的時候顯示的是input啊,想想v-show="todo.isEdit"
)。
但是這里邊有點兒問題,如果已經添加過了isEdit,那每次點擊編輯按鈕,都會添加一次isEdit屬性,這樣是不太好的,所以要加個判斷,添加過了就改成true,沒添加過就添加個true
handleEdit(todo) {if (todo.isEdit !== undefined) {console.log('todo里有isEdit屬性了')todo.isEdit = true;} else {console.log('todo里沒有isEdit屬性')this.$set(todo, 'isEdit', true);}this.$nextTick(function () {this.$refs.inputTitle.focus();})},
4、失去焦點傳數據
失去焦點首先input得變回span,然后使用全局事件總線傳值,傳值一定要傳當前input框的value值,因為這才是你修改后的值,使用事件對象獲取。(當然,別忘了id也要傳)
handleBlur(todo, e) {todo.isEdit = false;if (!e.target.value.trim()) return alert('值不能為空!'); //trim去掉空格this.$bus.$emit('updateTodo', todo.id, e.target.value);
}
5、App收數據
把input框里你寫的東西拿過來,給對應的todo.title
//6.實現編輯todo
methods: {......updateTodo(id, title) {this.todos.forEach((todo) => {if (todo.id === id) { todo.title = title }});}}
mounted() {......//實現編輯功能,接收數據this.$bus.$on('updateTodo', this.editTodo);
},
beforeDestroy() {//最好在銷毀前解綁this.$bus.$off('updateTodo');
},
$nextTick
1、語法:this.$nextTick(回調函數)
2、作用:在下一次 DOM 更新結束,v-for循環結束后
執行其指定的回調。
3、什么時候用:當改變數據后,要基于更新后的新DOM進行某些操作時(如input自動獲取焦點),要在nextTick所指定的回調函數中執行。
4、比如剛才點擊編輯后,input框沒法自動獲取焦點,那么我就得先點一下,再點別處兒才能切換回去,如果直接this.$refs.inputTitle.focus();
不行,因為這個函數雖然動了isEdit的值,但是模板重新解析也得等這個函數走完啊,那input還沒創建出來呢,就focus了,肯定是不行滴。
有個辦法就是用異步,也可以解決,但是更好的辦法是$nextTick
動畫與過渡
1、作用:在插入、更新或移除 DOM元素時,在合適的時候給元素添加樣式類名。
2、寫法:
準備好樣式:
元素進入的樣式:
v-enter:進入的起點
v-enter-active:進入過程中
v-enter-to:進入的終點
元素離開的樣式:
v-leave:離開的起點
v-leave-active:離開過程中
v-leave-to:離開的終點
使用<transition>
包裹要過度的元素,并配置name屬性:
<transition name="hello"><h1 v-show="isShow">你好啊!</h1>
</transition>
3、備注:若有多個元素需要過度,則需要使用:<transition-group>
,且每個元素都要指定key值。
進入離開動畫三種寫法
- 1、動畫(Test.vue)
<template><div><button @click="isShow = !isShow">顯示/隱藏</button><transition name="hello" appear><h1 v-show="isShow">你好呀!</h1></transition></div>
</template><script>
export default {name: 'Test',data() {return {isShow: true,}},
}
</script><style scoped>
h1 {background-color: pink;
}.hello-enter-active {animation: potato 1s;
}
.hello-leave-active {animation: potato 1s reverse;
}@keyframes potato {from {transform: translateX(-100%);}to {transform: translateX(0px);}
}
</style>
- 2、過渡動畫(Test2.vue)
<template><div><button @click="isShow = !isShow">顯示/隱藏</button><transition-group name="hello" appear><h1 v-show="isShow" key="1">你好呀!</h1><h1 v-show="isShow" key="2">小土豆</h1></transition-group></div>
</template><script>
export default {name: 'Test2',data() {return {isShow: true,}},
}
</script><style scoped>
h1 {background-color: skyblue;
}/* 進入的起點,離開的終點 */
.hello-enter,
.hello-leave-to {transform: translateX(-100%);
}
/* 進入的終點,離開的起點 */
.hello-enter-to,
.hello-leave {transform: translateX(0);
}
.hello-enter-active,
.hello-leave-active {transition: 0.5s linear;
}
</style>
- 3、集成第三方動畫
<template><div><button @click="isShow = !isShow">顯示/隱藏</button><transition-groupname="animate__animated animate__bounce"appearenter-active-class="animate__swing"leave-active-class="animate__backOutUp"><h1 v-show="isShow" key="1">你好呀!</h1><h1 v-show="isShow" key="2">小土豆</h1></transition-group></div>
</template><script>
import 'animate.css'
export default {name: 'Test3',data() {return {isShow: true,}},
}
</script><style scoped>
h1 {background-color: orange;
}
</style>
- App.vue
<template><div id="App"><Test /><Test2 /><Test3 /></div>
</template><script>
import Test from './components/Test'
import Test2 from './components/Test2'
import Test3 from './components/Test3'
export default {name: 'App',components: { Test, Test2, Test3 },
}
</script>
- 效果:(從上往下分別是Test、Test2、Test3效果)