Vue.js-狀態管理與Vuex

學習筆記:狀態管理與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個選項可以使用:getteractionsmodules

getter能將computed的方法提取出來,也可以依賴其他的getter,把getter作爲第二個參數。

actionmutation很像,不同的是action裏面提交的是mutation,並且可以一步操作業務邏輯。

action在組件內通過$store.dispatch觸發。

modules用來將store分割到不同模塊,當項目足夠大時,store裏的stategettersmutationsactions會非常多,使用modules可以把它們寫到不同的文件中。

modulemutationgetter接收的第一個參數state是當前模塊的狀態。在actionsgetters中還可以接收一個參數rootState,來訪問根節點的狀態。

實戰:中央事件總線插件vue-bus

中央事件總線bus作爲一個簡單的組件傳遞事件,用於解決跨級和兄弟組件通信的問題。

vue-bus插件給Vue添加一個屬性$bus,並代理$emit$on$off三個方法。

ES6語法

emit(event,..args)中的...是函數參數的解構。因爲不知道組件會傳遞多少個參數進來,使用...args可以把從當前參數到最後的參數都獲取到。

使用vue-bus有兩點需要注意:

  1. 第一是$bus.on應該在created鉤子內使用,如果在mounted使用,它可能接收不到其他組件來自created鉤子內發出的事件;
  2. 第二點是使用了$bus.onbeforeDestroy鉤子裏應該再使用$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。

前端路由,即由前端來維護一個路由規則。實現方式有兩種;

  1. 一種是利用urlhash,就是常說的錨點(#),JavaScript通過hashChange事件來監聽url的改變;
  2. 另一種就是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.js1.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裏設置modehistory會開啓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-activeclass,設置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提供了導航鉤子beforeEachafterEach,它們會在路由即將改變前和改變後觸發,所以設置標題可以在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文件組成。

exportimport是用來導出和導入模塊的。一個模塊就是一個js文件,它擁有獨立的作用域,裏面定義的變量外部是無法獲取的。

module對象的rules屬性中可以指定一系列的loaders,每一個loader都必須包含testuse兩個選項。

webpack編譯過程中遇到require()import語句導入一個後綴名爲.css的文件時,先將它通過css-loader轉換,再通過style-loader轉換,然後繼續打包。use選項的值可以是數組或字符串,如果是數組,它的編譯順序就是從後往前。

webpack的主要核心部分包括入口(Entry)出口(Output)加載器(Loaders)插件(Plugin)

單文件組件與vue-loader

<style>標籤使用scoped屬性,表示當前的CSS只在這個組件有效,如果不加,namediv的樣式會應用到整個項目。

使用.vue文件需要先安裝vue-loadervue-style-loader等加載器並做配置。如果要使用ES6語法,還需要安裝babelbabel-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對象就是定義時所在的對象,而不是使用時所在的對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章