Vue知識總結(2)
Vuex狀態管理
Vuex是專門為Vue應用程式提供的狀態管理模式,每個Vuex應用的核心是store
(倉庫),即裝載應用程式state
(狀態)的容器,每個應用通常只擁有一個store
例項。
Vuex的state
是響應式的,即store
中的state
發生變化時,相應元件也會進行更新,修改store
當中state
的唯一途徑是提交mutations
。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
store.commit("increment") // 通過store.state來獲取狀態物件
console.log(store.state.count) // 通過store.commit()改變狀態
State
從store
當中獲取state
的最簡單辦法是在計算屬性中返回指定的state
,每當state
發生改變的時候都會重新執行計算屬性,並且更新關聯的DOM。
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
Vuex提供store
選項,將state
從根元件注入到每個子元件中,從而避免頻繁import store
。
// 父元件中註冊store屬性
const app = new Vue({
el: "#app",
store: store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>`
})
// 子元件,store會注入到子元件,子元件可通過this.$store進行訪問
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
Vuex提供mapState()
輔助函式,避免使用多個state
的場景下,多次去宣告計算屬性。
// 在單獨構建的版本中輔助函式為 Vuex.mapState
import { mapState } from "vuex"
export default {
computed: mapState({
count: state => state.count,
// 傳遞字串引數"count"等同於`state => state.count`
countAlias: "count",
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
// 當計算屬性名稱與state子節點名稱相同時,可以向mapState傳遞一個字串陣列
computed: mapState([
"count" // 對映this.count到store.state.count
])
mapState()
函式返回一個包含有state
相關計算屬性的物件,這裡可以通過ES6的物件展開運算子...
將該物件與Vue元件本身的computed
屬性進行合併。
computed: {
localComputed () {},
...mapState({})
}
Vuex允許在store
中定義getters
(可視為store的計算屬性),getters
的返回值會根據其依賴被快取,只有當依賴值發生了改變才會被重新計算。該方法接收state
作為第1個引數,其它getters
作為第2個引數。可以直接在store
上呼叫getters
來獲取指定的計算值。
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: "...", done: true },
{ id: 2, text: "...", done: false }
]
},
getters: {
doneTodos: (state, getters) => {
return state.todos.filter(todo => todo.done)
}
}
})
// 獲取doneTodos = [{ id: 1, text: "...", done: true }]
store.getters.doneTodos
這樣就可以方便的根據store
中現有的state
派生出新的state
,從而避免在多個元件中複用時造成程式碼冗餘。
computed: {
doneTodosCount () {
// 現在可以方便的在Vue元件使用store中定義的doneTodos
return this.$store.getters.doneTodos
}
}
Vuex提供的mapGetters()
輔助函式將store
中的getters
對映到區域性計算屬性。
import { mapGetters } from "vuex"
export default {
computed: {
// 使用物件展開運算子將getters混入computed計算屬性
...mapGetters([
"doneTodosCount",
// 對映store.getters.doneTodosCount到別名this.doneCount
doneCount: "doneTodosCount"
])
}
}
Mutations
修改store中的state的唯一方法是提交mutation([mjuː"teɪʃ(ə)n] n.變化),mutations類似於自定義事件,擁有一個字串事件型別和一個回撥函式(接收state作為引數,是對state進行修改的位置)。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
// 觸發型別為increment的mutation時被呼叫
increment (state) {
state.count++ // 變更狀態
}
}
})
// 觸發mutation
store.commit("increment")
可以通過store的commit()
方法觸發指定的mutations,也可以通過store.commit()
向mutation傳遞引數。
// commit()
store.commit({
type: "increment",
amount: 10
})
// store
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
mutation事件型別建議使用常量,並且將這些常量放置在單獨檔案,便於管理和防止重複。
// mutation-types.js
export const SOME_MUTATION = "SOME_MUTATION"
// store.js
import Vuex from "vuex"
import { SOME_MUTATION } from "./mutation-types"
const store = new Vuex.Store({
state: { ... },
mutations: {
// 可以通過ES6的計算屬性命名特性去使用常量作為函式名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
mutation()
必須是同步函式,因為devtool無法追蹤回撥函式中對state
進行的非同步修改。
Vue元件可以使用this.$store.commit("xxx")
提交mutation,或者使用mapMutations()
將Vue元件中的methods
對映為store.commit
呼叫(需要在根節點注入store
)。
import { mapMutations } from "vuex"
export default {
methods: {
...mapMutations([
"increment" // 對映this.increment()為this.$store.commit("increment")
]),
...mapMutations({
add: "increment" // 對映this.add()為this.$store.commit("increment")
})
}
}
Actions
Action用來提交mutation,且Action中可以包含非同步操作。Action函式接受一個與store例項具有相同方法和屬性的context
物件,因此可以通過呼叫context.commit
提交一個mutation,或者通過context.state
和context.getters
來獲取state、getters。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit("increment")
}
}
})
生產環境下,可以通過ES6的解構引數來簡化程式碼。
actions: {
// 直接向action傳遞commit方法
increment ({ commit }) {
commit("increment")
}
}
Action通過store.dispatch()
方法進行分發,mutation當中只能進行同步操作,而action內部可以進行非同步的操作。下面是一個購物車的例子,程式碼中分發了多個mutations,並進行了非同步API操作。
actions: {
checkout ({ commit, state }, products) {
const savedCartItems = [...state.cart.added] // 把當前購物車的物品備份起來
commit(types.CHECKOUT_REQUEST) // 發出結賬請求,然後清空購物車
// 購物Promise分別接收成功和失敗的回撥
shop.buyProducts(
products,
() => commit(types.CHECKOUT_SUCCESS), // 成功操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems) // 失敗操作
)
}
}
元件中可以使用this.$store.dispatch("xxx")
分發action,或者使用mapActions()
將元件的methods
對映為store.dispatch
(需要在根節點注入store
)。
import { mapActions } from "vuex"
export default {
methods: {
...mapActions([
"increment" // 對映this.increment()為this.$store.dispatch("increment")
]),
...mapActions({
add: "increment" // 對映this.add()為this.$store.dispatch("increment")
})
}
}
store.dispatch
可以處理action
回撥函式當中返回的Promise
,並且store.dispatch
本身仍然返回一個Promise
。
actions: {
// 定義一個返回Promise物件的actionA
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit("someMutation") // 觸發mutation
resolve()
}, 1000)
})
},
// 也可以在actionB中分發actionA
actionB ({ dispatch, commit }) {
return dispatch("actionA").then(() => {
commit("someOtherMutation") // 觸發另外一個mutation
})
}
}
// 現在可以分發actionA
store.dispatch("actionA").then(() => {
... ... ...
})
可以體驗通過ES7的非同步處理特性async
/await
來組合action。
actions: {
async actionA ({ commit }) {
commit("gotData", await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch("actionA") //等待actionA完成
commit("gotOtherData", await getOtherData())
}
}
Module
整個應用使用單一狀態樹的情況下,所有state都會集中到一個store物件,因此store可能變得非常臃腫。因此,Vuex允許將store切割成模組(module),每個模組擁有自己的state
、mutation
、action
、getter
、甚至是巢狀的子模組。
const moduleA = {
state: {},
mutations: {},
actions: {},
getters: {}
}
const moduleB = {
state: {},
mutations: {},
actions: {}
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // moduleA的狀態
store.state.b // moduleB的狀態
module內部的mutations()
和getters()
接收的第1個引數是模組的區域性狀態物件。
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
state.count++ // 這裡的state是模組的區域性狀態
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
模組內部action當中,可以通過context.state
獲取區域性狀態,以及context.rootState
獲取全域性狀態。
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit("increment")
}
}
}
}
模組內部的getters()
方法,可以通過其第3個引數接收到全域性狀態。
const moduleA = {
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
嚴格模式
嚴格模式下,如果state變化不是由mutation()
函式引起,將會丟擲錯誤。只需要在建立store
的時候傳入strict: true
即可開啟嚴格模式。
const store = new Vuex.Store({
strict: true
})
不要在生產環境下啟用嚴格模式,因為它會深度檢測不合法的state變化,從而造成不必要的效能損失,我們可以通過在構建工具中增加如下判斷避免這種情況。
const store = new Vuex.Store({
strict: process.env.NODE_ENV !== "production"
})
嚴格模式下,在屬於Vuex的state上使用v-model指令會丟擲錯誤,此時需要手動繫結value並監聽input、change事件,並在事件回撥中手動提交action。另外一種實現方式是直接重寫計算屬性的get和set方法。
總結
- 應用層級的狀態應該集中到單個store物件中。
- 提交
mutation
是更改狀態的唯一方法,並且這個過程是同步的。 -
非同步邏輯都應該封裝到
action
裡面。
Webpack Vue Loader
vue-loader是由Vue開源社群提供的Webpack載入器,用來將.vue
字尾的單檔案元件轉換為JavaScript模組,每個.vue
單檔案元件可以包括以下部分:
- 一個
<template>
。 - 一個
<script>
。 - 多個
<style>
。
<template>只能有1個</template>
<script>只能有1個</script>
<style>可以有多個</style>
<style>可以有多個</style>
<style>可以有多個</style>
CSS作用域
向.vue
單檔案元件的<style>
標籤上新增scoped
屬性,可以讓該<style>
標籤中的樣式只作用於當前元件。使用scoped時,樣式選擇器儘量使用class或者id,以提升頁面渲染效能。
<!-- 單檔案元件定義 -->
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">Hank</div>
</template>
<!-- 轉換結果 -->
<style>
.example[data-v-f3f3eg9] {
color: blue;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>Hank</div>
</template>
可以在一個元件中同時使用帶
scoped
屬性和不帶該屬性的<style/>
,分別用來定義元件私有樣式和全域性樣式。
CSS模組化
在單檔案元件.vue
的<style>
標籤上新增module
屬性即可開啟CSS模組化特性。CSS Modules用於模組化組合CSS,vue-loader已經整合了CSS模組化特性。
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
CSS模組會向Vue元件中注入名為$style
計算屬性,從而實現在元件的<template/>
中使用動態的class
屬性進行繫結。
<template>
<p :class="$style.red">
This should be red
</p>
</template>
動畫
Vue在插入、更新、移除DOM的時候,提供瞭如下幾種方式去展現進入(entering)和離開(leaving)的過渡效果。
- 在CSS過渡和動畫中應用class。
- 鉤子過渡函式中直接操作DOM。
- 使用CSS、JavaScript動畫庫,如Animate.css、Velocity.js。
transition元件
Vue提供了內建元件<transition/>
來為HTML元素、Vue元件新增過渡動畫效果,可以在條件展示(使用v-if
或v-show
)、動態元件、展示元件根節點的情況下進行渲染。<transition/>
主要用來處理單個節點,或者同時渲染多個節點當中的一個。
自動切換的class類名
在元件或HTML進入(entering)和離開(leaving)的過渡效果當中,Vue將會自動切換並應用下圖中的六種class類名。
可以使用<transition/>
的name
屬性來自動生成過渡class類名,例如下面例子中的name: "fade"
將自動擴充為.fade-enter
,.fade-enter-active
等,name
屬性預設的情況下預設類名為v
。
<div id="demo">
<button v-on:click="show = !show"> Toggle </button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
<script>
new Vue({
el: "#demo",
data: {
show: true
}
})
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-to {
opacity: 0
}
</style>
自定義CSS類名
結合Animate.css
使用時,可以在<transition/>
當中通過以下屬性自定義class類名。
<transition
enter-class = "animated"
enter-active-class = "animated"
enter-to-class = "animated"
leave-class = "animated"
leave-active-class = "animated"
leave-to-class = "animated">
</transition>
自定義JavaScript鉤子
結合Velocity.js
使用時,通過v-on在屬性中設定鉤子函式。
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled">
</transition>
<script>
// ...
methods: {
beforeEnter: function (el) {},
enter: function (el, done) { done() },
afterEnter: function (el) {},
enterCancelled: function (el) {},
beforeLeave: function (el) {},
leave: function (el, done) { done() },
afterLeave: function (el) {},
leaveCancelled: function (el) {} // 僅用於v-show
}
</script>
顯式設定過渡持續時間
可以使用<transition>
上的duration屬性
設定一個以毫秒為單位的顯式過渡持續時間。
<transition :duration="1000"> Hank </transition>
<!-- 可以分別定製進入、移出的持續時間 -->
<transition :duration="{ enter: 500, leave: 800 }"> Hank </transition>
元件首次渲染時的過渡
通過<transition>
上的appear屬性
設定元件節點首次被渲染時的過渡動畫。
<!-- 自定義CSS類名 -->
<transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class"
appear-active-class="custom-appear-active-class">
</transition>
<!-- 自定義JavaScript鉤子 -->
<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook">
</transition>
HTML元素的過渡效果
Vue元件的key屬性
key屬性主要用在Vue虛擬DOM演算法中去區分新舊VNodes,不顯式使用key
的時候,Vue會使用效能最優的自動比較演算法。顯式的使用key
,則會基於key
的變化重新排列元素順序,並移除不存在key
的元素。具有相同父元素的子元素必須有獨特的key
,因為重複的key
會造成渲染錯誤。
<ul>
<!-- 最常見的用法是在使用v-for的時候 -->
<li v-for="item in items" :key="item.id">...</li>
</ul>
元素的的交替過渡
可以通過Vue提供的v-if
和v-else
屬性來實現多元件的交替過渡,最常見的過渡效果是一個列表以及描述列表為空時的訊息。
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>
Vue中具有相同名稱的元素切換時,需要通過關鍵字key
作為標記進行區分,否則Vue出於效率的考慮只會替換相同標籤內的內容,因此為<transition>
元件中的同名元素設定key
是一個最佳實踐。
<transition>
<button v-if="isEditing" key="save"> Save </button>
<button v-else key="edit"> Edit </button>
</transition>
一些場景中,可以通過給相同HTML元素的key
屬性設定不同的狀態來代替冗長的v-if
和v-else
。
<!-- 通過v-if和v-else來實現 -->
<transition>
<button v-if="isEditing" key="save"> Save </button>
<button v-else key="edit"> Edit </button>
</transition>
<!-- 設定動態的key屬性來實現 -->
<transition>
<button v-bind:key="isEditing"> {{isEditing ? "Save" : "Edit"}} </button>
</transition>
而對於使用了多個v-if
的多元素過渡,也可以通過動態的key
屬性進行大幅度的簡化。
<!-- 多個v-if實現的多元素過渡 -->
<transition>
<button v-if="docState === "saved"" key="saved"> Edit </button>
<button v-if="docState === "edited"" key="edited"> Save </button>
<button v-if="docState === "editing"" key="editing"> Cancel </button>
</transition>
<!-- 通過動態key屬性可以大幅簡化模板程式碼 -->
<transition>
<button v-bind:key="docState"> {{ buttonMessage }} </button>
</transition>
<script>
...
computed: {
buttonMessage: function () {
switch (this.docState) {
case "saved": return "Edit"
case "edited": return "Save"
case "editing": return "Cancel"
}
}
}
</script>
Vue元件的過渡效果
多個Vue元件之間的過渡不需要使用key
屬性,只需要使用動態元件即可。
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
<script>
new Vue({
el: "#transition-components-demo",
data: {
view: "v-a"
},
components: {
"v-a": {
template: "<div>Component A</div>"
},
"v-b": {
template: "<div>Component B</div>"
}
}
})
<script>
<style>
.component-fade-enter-active, .component-fade-leave-active {
transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to {
opacity: 0;
}
<style>
選擇HTML元素或Vue元件的過渡模式
<transition>
的預設進入(enter)和離開(leave)行為同時發生,所以當多個需要切換顯示的HTML元素或Vue元件處於相同位置的時候,這種同時生效的進入和離開過渡不能滿足所有需求,Vue可以通過<transition-gruop>
元件的mode
屬性來選擇如下過渡模式。
-
in-out
:新元素先進行過渡,完成之後當前顯示的元素再過渡離開。 -
out-in
:當前顯示的元素先進行過渡,完成之後新元素再過渡進入。
<transition name="fade" mode="out-in">
<button v-if="docState === "saved"" key="saved"> Edit </button>
<button v-if="docState === "edited"" key="edited"> Save </button>
<button v-if="docState === "editing"" key="editing"> Cancel </button>
</transition>
transition-group元件
<transition-group>
用來設定多個HTML元素或Vue元件的過渡效果,不同於<transition>
,該元件預設會被渲染為一個真實的<span>
元素,但是開發人員也可以通過<transition-group>
元件的tag
屬性更換為其它合法的HTML元素。<transition-group>
元件內部的元素必須要提供唯一的key
屬性值。
<div id="list-demo" class="demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
<script>
new Vue({
el: "#list-demo",
data: {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
}
})
</script>
<style>
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to {
opacity: 0;
transform: translateY(30px);
}
</style>
<transition-group>
實現的列表過渡效果在新增、移除某個HTML元素時,相臨的其它HTML元素會瞬間移動至新位置,這個過程並非平滑的過渡。為解決這個問題,<transition-group>
提供v-move特性去覆蓋移動過渡期間所使用的CSS類名。開啟該特性,即可以通過name
屬性手動設定(下面例子中的name="flip-list"
與.flip-list-move
),也可以直接使用move-class
屬性。
<div id="flip-list-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<transition-group name="flip-list" tag="ul">
<li v-for="item in items" v-bind:key="item">
{{ item }}
</li>
</transition-group>
</div>
<script>
new Vue({
el: "#flip-list-demo",
data: {
items: [1,2,3,4,5,6,7,8,9]
},
methods: {
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
})
</script>
<style>
.flip-list-move {
transition: transform 1s;
}
<style>
可以通過響應式的繫結<transition>
和<transition-gruop>
上的name屬性,從而能夠根據元件自身的狀態實現具有動態性的過渡效果。
<transition v-bind:name="transitionName"></transition>
相關文章
- 前端知識點總結——Vue前端Vue
- Vue學習知識點總結Vue
- JS基礎知識總結(2)JS
- Vue一些知識點總結Vue
- Vue 路由知識點歸納總結Vue路由
- Vue知識精簡總結-更新中Vue
- yii2 基礎知識總結
- servlet知識總結Servlet
- Cookie知識總結(-)Cookie
- MySQL知識總結MySql
- 知識點總結
- 知識方法總結
- Docker知識總結Docker
- JQuery知識總結jQuery
- Redis知識總結Redis
- 圖知識總結
- golang知識總結Golang
- 常量知識總結
- servelt知識總結
- Vue.js中前端知識點總結筆記Vue.js前端筆記
- influxdb知識總結(2)--- influxdb 中的重要概念UX
- Vue重要知識小結Vue
- Java 知識點總結Java
- django知識點總結Django
- iOS 知識點總結iOS
- MongoDB知識點總結MongoDB
- HDFS知識點總結
- HBase知識點總結
- jQuery 知識點總結jQuery
- Kafka知識點總結Kafka
- Tomcat 知識點總結Tomcat
- MySQL知識點總結MySql
- 概率論知識總結
- Vue知識點總結(3)——v-bind(超級詳細)Vue
- 移動前端知識總結前端
- Java基礎知識總結Java
- React 基礎知識總結React
- 知識點漏缺總結