學習筆記:狀態管理與Vuex
狀態管理與Vuex
非父子組件(跨級組件和兄弟組件)通信時,使用了bus
(中央事件總線)的一個方法,用來觸發和接收事件,進一步起到通信的作用。
一個組件可以分爲數據(model
)和視圖(view
),數據更新時,視圖也會自動更新。在視圖中又可以綁定一個事件,它們觸發methods
裏指定的方法,從而可以改變數據、更新視圖,這是一個組件基本的運行模式。
const store = new Vuex.Store({});
倉庫store
包含了應用的數據(狀態)和操作過程。Vuex裏的數據都是響應式的,任何組件使用同一store
的數據時,只要store
的數據變化,對應的組件也會立即更新。
數據保存在Vuex選項的state
字段內。
const store = new Vuex.Store({
state: {
count: 0
}
});
在任何組件內,可以直接通過$store.state.count
讀取。
<template>
<div>
<h1>首頁</h1>
{{$store.state.count}}
</div>
</template>
直接卸載template
裏顯得有點亂,可以用一個計算屬性來顯示:
<div>
<h1>首頁</h1>
{{count}}
</div>
export default {
computed: {
count() {
return $store.state.count;
}
}
}
在組件內來自store
的數據只能讀取,不能手動修改,修改store
中數據的唯一途徑是顯式地提交mutations
。
mutations
是Vuex的第二個選項,用來直接修改state
裏的數據。
在組件內,通過this.$store.commit
方法來執行mutations
。
mutations
還可以接受第二個參數,可以是數字、字符串或對象等類型。
ES6語法
函數的參數可以設定默認值,當沒有傳入該參數時,使用設置的值。
increment(state,n=1)等同於:
increment(state,n){
n=n||1;
}
提交mutations
的另一種方式是直接使用包含type
屬性的對象。
mutations
裏儘量不要異步操作數據,否則組件在commit
後數據不能立即改變,而且不知道什麼時候會改變。
高級用法
Vuex還有其他3個選項可以使用:getter
、actions
、modules
。
getter
能將computed
的方法提取出來,也可以依賴其他的getter
,把getter
作爲第二個參數。
action
與mutation
很像,不同的是action
裏面提交的是mutation
,並且可以一步操作業務邏輯。
action
在組件內通過$store.dispatch
觸發。
modules
用來將store
分割到不同模塊,當項目足夠大時,store
裏的state
、getters
、mutations
、actions
會非常多,使用modules
可以把它們寫到不同的文件中。
module
的mutation
和getter
接收的第一個參數state
是當前模塊的狀態。在actions
和getters
中還可以接收一個參數rootState
,來訪問根節點的狀態。
實戰:中央事件總線插件vue-bus
中央事件總線bus
作爲一個簡單的組件傳遞事件,用於解決跨級和兄弟組件通信的問題。
vue-bus
插件給Vue添加一個屬性$bus
,並代理$emit
、$on
、$off
三個方法。
ES6語法
emit(event,..args)
中的...
是函數參數的解構。因爲不知道組件會傳遞多少個參數進來,使用...args
可以把從當前參數到最後的參數都獲取到。
使用vue-bus
有兩點需要注意:
- 第一是
$bus.on
應該在created
鉤子內使用,如果在mounted
使用,它可能接收不到其他組件來自created
鉤子內發出的事件; - 第二點是使用了
$bus.on
在beforeDestroy
鉤子裏應該再使用$bus.off
解除,因爲組件銷燬後,就沒有必要把監聽的句柄存儲在vue-bus
中。
Vue插件
註冊插件需要一個公開的方法install
,它的第一個參數時Vue構造器,第二個參數是一個可選的選項對象。
<p data-height="350" data-theme-id="0" data-slug-hash="RJVOXd" data-default-tab="js" data-user="whjin" data-embed-version="2" data-pen-title="Vue插件" class="codepen">See the Pen Vue插件 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
前端路由與vue-router
SPA的核心就是前端路由,對於一個網址,每次GET或POST等請求在服務端有一個專門的正則配置列表,然後匹配到具體的一條路徑後,分發到不同的Controller,進行各種操作,最終將html
或數據返回給前端,這樣就完成了一次IO。
前端路由,即由前端來維護一個路由規則。實現方式有兩種;
- 一種是利用
url
的hash
,就是常說的錨點(#
),JavaScript通過hashChange
事件來監聽url
的改變; - 另一種就是HTML5的
history
模式,它使url
看起來像普通網站那樣,以/
分割,沒有#
,但也沒並沒有跳轉,不過使用這種模式需要服務端支持,服務端在接收到所有的請求後,都指向同一個html
文件,不然會出現404。
因此,SPA只有一個html
,整個網站所有的內容都在這個html
裏,通過JavaScript來處理。
如果要獨立開發一個前端路由,需要考慮到頁面的可插拔、生命週期、內存管理等問題。
vue-router
vue-router
的實現原理與通過is
特性實現動態組件的方法類似,路由不同的頁面事實上就是動態加載不同的組件。
創建一個數組來指定路由匹配列表,每一個路由映射一個組件:
const Routers = [
{
path: '/index',
component: (resolve) => require(['./views/index.vue'], resolve)
},
{
path: '/about',
component: (resolve) => require(['./views/about.vue'], resolve)
}
];
Routers裏每一項的path
屬性就是指定當前匹配的路徑,component
是映射的組件。
webpack
會把每一個路由都打包爲一個js
文件,在請求道該頁面時,再去加載這個頁面的js
,也就是異步實現的懶加載(按需加載)。這樣做的好處是不需要在打開首頁的時候就把所有的頁面內容全部加載進來,只在訪問時才加載。
使用了異步路由後,變移除的每個頁面的js
都叫做chunk
(塊),它們命名默認是0.main.js
、1.main.js
...
可以在webpack
配置的出口output
裏通過設置chunkFilename
字段修改chunk
命名。
output: {
publicPath: "/dist/",
filename: "[name].js",
chunkFilename: "[name].chunk.js"
}
有了chunk
後,在每個頁面(.vue
文件)裏寫的樣式也需要配置後纔會打包進main.css
,否則仍然會通過JavaScript動態創建<style>
標籤的形式寫入。
const RouterConfig = {
//使用HTML5的History路由模式
mode: 'history',
routes: Routers
};
const router = new VueRouter(RouterConfig);
new Vue({
el: "#app",
router: router,
render: h => {
return h(App)
}
});
在RouterConfig裏設置mode
爲history
會開啓HTML5的History路由模式,通過/
設置路徑。如果不配置mode
,就會使用#
來設置路徑。
開啓History路由,在生產環境時必須進行配置,將所有路由都指向同一個html
,或設置404頁面,否則刷新時頁面就會出現404。
在路由列表裏,可以在最後新加一項,當訪問的路徑不存在時,重定向到首頁:
{
path: '*',
redirect: '/index'
}
路由列表的path
可以帶參數,比如/user/123
,其中用戶ID123
是動態的,但它們路由到同一個頁面,在這個頁面裏,期望獲取這個ID,然互毆請求相關數據。
跳轉
vue-router
有兩種跳轉頁面的方法,第一種是使用內置的<router-link>
組件,它會被渲染爲一個<a>
標籤。
<template>
<div>
<h1>首頁</h1>
<router-link to="/about">跳轉到about</router-link>
</div>
</template>
它的用法與一般的組件一樣,to
是一個prop
,指定需要跳轉的路徑,也可以用v-bind
動態設置。
使用<router-link>
,在HTML5的History模式下會攔截點擊,避免瀏覽器重新加載頁面。
<router-view>還有其他一些prop
,常用的有:
-
tag
可以指定渲染成什麼標籤,比如<router-link to="/about" tag="li">
渲染的結果就是<li>
,而不是<a>
-
replace
使用replace
不會留下History記錄,所以導航後不能用後退鍵返回上一個頁面,如<router-link to="/about" replace>
-
active-class
當<router-link>
對應的路由匹配成功時,會自定給當前元素設置一個名爲router-link-active
的class
,設置prop:active-class
可以修改默認的名稱。在做類似導航欄時,可以使用該功能高亮顯示當前頁面對應的導航欄單項,但是一般不會修改active-class
,直接使用默認值router-link-active
。
有時候,跳轉頁面可能需要在JavaScript中進行,類似於window.location.href
。這時可以使用第二種跳轉方法,使用router
實例的方法。
<template>
<div>
<h1>介紹頁</h1>
<button @click="handleRouter">跳轉到user</button>
</div>
</template>
<script>
export default {
methods: {
handleRouter() {
this.$router.push('/user/123');
}
}
}
</script>
$router
還有其他一些方法:
-
replace
類似於<router-link>
的replace
功能,它不會向history
添加新紀錄,而是替換掉當前的history
記錄,如this.$router.replace('/user/123')
-
go
類似於window.history.go()
,在history
記錄中向前或後退多少步,參數是整數
高級用法
在SPA項目中,如何修改網頁的標題?
在頁面發生路由變化時,統一設置。
vue-router
提供了導航鉤子beforeEach
和afterEach
,它們會在路由即將改變前和改變後觸發,所以設置標題可以在beforeEach
鉤子完成。
<p data-height="365" data-theme-id="0" data-slug-hash="gKRaLm" data-default-tab="js" data-user="whjin" data-embed-version="2" data-pen-title="vue-router導航鉤子" class="codepen">See the Pen vue-router導航鉤子 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
導航鉤子有3個參數:
-
to
即將要進入的目標的路由對象 -
from
當前導航即將要離開的路由對象 -
next
調用該方法後,才能進入下一個鉤子
路由列表的meta
字段可以自定義一些信息,將每個頁面的title
寫入meta
來統一維護,beforeEach
鉤子可以從路由對象to
裏獲取meta
信息,從而改變標題。
某些頁面需要校驗是否登錄,如果登錄就可以訪問,否則跳轉到登錄頁。通過localStorage
來簡單判斷是否登錄。
router.beforeEach((to, from, next) => {
if (window.localStorage.getItem('token')) {
next()
} else {
next('/login')
}
});
next()
的參數設置爲false
,可以取消導航,設置爲具體的路徑可以導航到指定的頁面。
使用webpack構建
webpack的主要適用場景時單頁面富應用(SPA)。SPA通過是由一個html
文件和一堆按需加載的js
文件組成。
export
和import
是用來導出和導入模塊的。一個模塊就是一個js
文件,它擁有獨立的作用域,裏面定義的變量外部是無法獲取的。
在module
對象的rules
屬性中可以指定一系列的loaders
,每一個loader
都必須包含test
和use
兩個選項。
當webpack
編譯過程中遇到require()
或import
語句導入一個後綴名爲.css
的文件時,先將它通過css-loader
轉換,再通過style-loader
轉換,然後繼續打包。use
選項的值可以是數組或字符串,如果是數組,它的編譯順序就是從後往前。
webpack的主要核心部分包括入口(Entry)、出口(Output)、加載器(Loaders)、插件(Plugin)。
單文件組件與vue-loader
<style>
標籤使用scoped
屬性,表示當前的CSS只在這個組件有效,如果不加,namediv
的樣式會應用到整個項目。
使用.vue
文件需要先安裝vue-loader
、vue-style-loader
等加載器並做配置。如果要使用ES6語法,還需要安裝babel
和babel-loader
等加載器。
<p data-height="465" data-theme-id="0" data-slug-hash="NzjNgp" data-default-tab="js" data-user="whjin" data-embed-version="2" data-pen-title="Vue-webpack.config.js" class="codepen">See the Pen Vue-webpack.config.js by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
新建.babelrc
文件,並寫入babel
的配置,webpack會依賴此配置文件來使用babel
編譯ES6代碼。
{
"presets": ["es2015"],
"plugins": ["transform-runtime"],
"comments": false
}
每個.vue
文件代表一個組件,組件之間可以相互依賴。
ES語法:
=>
是箭頭函數
render: h=>h(App)等同於
render: function(h) {
return h(App)
}
也等同於:
render: h=>{
return h(App)
}
箭頭函數裏的this
指向與普通函數不一樣,箭頭函數體內的this
對象就是定義時所在的對象,而不是使用時所在的對象。