系列教程之Vuex詳解及例子

一、Vuex是什麼?

介紹:Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。

理解:核心就是 store(倉庫),倉庫是用來幹什麼的?你就當它用來儲存東西的。

 

二、上方介紹提到的狀態管理模式是什麼?

     首先我們先看一張圖:  

這個狀態自管理應用包含以下幾個部分:

  • state,驅動應用的數據源;
  • view,以聲明方式將 state 映射到視圖;
  • actions,響應在 view 上的用戶輸入導致的狀態變化。
new Vue({
  // state
  data () {
    return {
      count: 0
    }
  },
  // view
  template: `
    <div>{{ count }}</div>
  `,
  // actions
  methods: {
    increment () {
      this.count++
    }
  }
})

三、我們什麼時候應該用到Vuex呢?

1.小應用不建議使用Vuex,因爲小項目使用 Vuex 可能會比較繁瑣冗餘;

2.中大型單頁應用,因爲要考慮如何更好地在組件外部管理狀態,Vuex 將會成爲自然而然的選擇;

 

四、對於使用Vuex的理解是什麼?

       由於Vue是單向數據流,子組件內部不能直接修改從父級傳遞過來的數據,子組件與子組件之間無法相互傳遞數據。如果我們想讓兩個子組件之間進行通信的話,可以藉助子組件 A 向父組件傳值,父組件接收子組件 A 的數據後再傳給 B 組件這樣的方式進行通信。

       但是這樣會有一個問題,就是如果子組件 A 的父組件上面還有一層爺爺組件,或者還有更多祖父類型的層級,那麼是不是會很麻煩。

       因此,我們會想到能不能我們將一個共有的數據存在一個特定的地方,用的時候自己去拿,這樣就不需要一層層傳值,於是乎 Vuex 就應運而生了。

 

五、核心概念

有五種屬性,分別是 State、 Getter、Mutation 、Action、 Module。

State  # 單一狀態樹

Vuex 使用單一狀態樹——是的,用一個對象就包含了全部的應用層級狀態。至此它便作爲一個“唯一數據源 (SSOT)”而存在。這也意味着,每個應用將僅僅包含一個 store 實例。單一狀態樹讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。

Getter

Vuex 允許我們在 store 中定義“getter”(可以認爲是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被重新計算。

Mutation

更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方,並且它會接受 state 作爲第一個參數:

Action

Action 類似於 mutation,不同在於:

  • Action 提交的是 mutation,而不是直接變更狀態。
  • Action 可以包含任意異步操作。

Module

由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。爲了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割:

 

六、創建項目,開始實戰

安裝vuex:

npm install vuex --save
項目目錄(具體處已用箭頭標註):

目錄我們可以看到有一個父組件HelloWorld和兩個子組件JobList和JobTop。我們接下來就在這幾個父子組件裏面來介紹傳值方法;

 

HelloWorld.vue

我們在父組件裏面引入兩個子組件,並呈現出數據,方便接下來我們繼續傳值操作:

<template>
  <div>
       <job-top :job="job"></job-top>
       <job-list :jobs="jobs"></job-list>
  </div>
</template>

<script>
import JobTop from './component/JobTop';
import JobList from './component/JobList';

export default {
  name: 'HelloWorld',
  props: {},
  data () {
    return {
        job:'',
        jobs:[]
    }
  },
  components:{
     JobTop,
     JobList,
  },
  methods: {
     getListInfo() {
       //這裏模擬的接口數據
       this.getListInfoSucc({
            "code":true,
            "job":"web",
            "data":{
                "jobs":[
                    {
                        "id":1,
                        "name":"web"
                    },
                    {
                        "id":2,
                        "name":"C++"
                    },
                    {
                        "id":3,
                        "name":"python"
                    },
                    {
                        "id":4,
                        "name":"java"
                    }
                ]
            }
        })
     },
     getListInfoSucc(res){
       if(res.code){
          this.job=res.job;
          this.jobs=res.data.jobs;
       }
     },
  },
  mounted() {
     this.getListInfo();
  },
}
</script>

<style scoped>
</style>

JobTop.vue

這裏我們繼續單個數據呈現,到時候方便顯示切換後的數據:

<template>
      <p>{{job}}</p>
</template>
 
<script>
export default {
    name:'JobTop',
    props:{
        job:String,
    },
    data(){
        return{
            
        }
    },
}
</script>

<style>
</style>

JobList.vue

這裏是父組件裏面通過接口獲取的數組,進行遍歷所呈現出列表形式,我們之後再進行點擊傳值

<template>
     <div>
         <button v-for="item of jobs" :key="item.id">{{item.name}}</button>
     </div>
</template>
 
<script>
export default {
    name:'JobList',
    props:{
        jobs:Array,
    },
    data(){
        return{    
        }
    },
}
</script>

<style>
</style>

頁面呈現效果如下:

好,現在頁面呈現出來了,我接着要寫點擊事件來進行傳值,我們會想到三種傳值方式:

1.父子組件傳值

在 jobList.vue 中定義 jobClick 方法,將 changeJob 方法注入,並將所選中的 job 值傳入

<template>
     <div>
         <button v-for="item of jobs" :key="item.id" @click="jobClick(item.name)">{{item.name}}</button>
     </div>
</template>
 
<script>
export default {
    name:'JobList',
    props:{
        jobs:Array,
    },
    data(){
        return{   
        }
    },
    methods:{
        //父組件傳值
        jobClick(name){
            this.$emit("changeJob",name)
        }
    }
}
</script>

<style>
</style>

在父組件 HelloWorld.vue 中接收

<template>
  <div>
       <job-top :job="job"></job-top>
       <job-list :jobs="jobs" @changeJob="ChangeJobClick"></job-list>
  </div>
</template>

<script>
import JobTop from './component/JobTop';
import JobList from './component/JobList';

export default {
  name: 'HelloWorld',
  props: {},
  data () {
    return {
        job:'',
        jobs:[]
    }
  },
  components:{
     JobTop,
     JobList,
  },
  methods: {
     getListInfo() {
       this.getListInfoSucc({
            "code":true,
            "job":"web",
            "data":{
                "jobs":[
                    {
                        "id":1,
                        "name":"web"
                    },
                    {
                        "id":2,
                        "name":"C++"
                    },
                    {
                        "id":3,
                        "name":"python"
                    },
                    {
                        "id":4,
                        "name":"java"
                    }
                ]
            }
        })
     },
     getListInfoSucc(res){
       if(res.code){
          this.job=res.job;
          this.jobs=res.data.jobs;
       }
     },
     ChangeJobClick(name){
       console.log("選中的崗位",name);
       this.job=name;
     }
  },
  mounted() {
     this.getListInfo();
  },
}
</script>

<style scoped>
</style>

在父組件的的 job-list 標籤中寫上子組件 JobLict.vue注入的 changeJob方法,並指向ChangeJobClick 方法,ChangeJobClick 方法中接收傳過來的選中的 job 值,並將該值傳遞給子組件 JobTop.vue,JobTop頁面無需改動,頁面效果如下:

 

 

2、原型鏈繼承

正好之前一篇博客講過 javascript 的原型鏈繼承,vue 其實就是 js 的一種框架,它也有prototype 屬性,我們叫做 Bus總線方法。如下:

JobList.vue

<template>
     <div>
         <button v-for="item of jobs" :key="item.id" @click="jobClick(item.name)">{{item.name}}</button>
     </div>
</template>
 
<script>
import Vue from 'vue'
Vue.prototype.bus=new Vue();
export default {
    name:'JobList',
    props:{
        jobs:Array,
    },
    data(){
        return{  
        }
    },
    methods:{
        //原型鏈繼承
        jobClick(name){
            console.log("點擊某個崗位:",name)
            this.bus.$emit("changeJob",name)
        }
    }
}
</script>

<style>
</style>

我們現在 Vue 的 prototype 屬性中引入 bus ,然後在 button 按鈕上綁定 jobClick 點擊事件,並傳入點擊的 job ,然後在 bus總線上注入 changeJob 方法,並將點擊的 job 值傳入,我們再來看一下 

JobTop.vue

<template>
      <p>{{jobself}}</p>
</template>
 
<script>
export default {
    name:'JobTop',
    props:{
        job:String,
    },
    data(){
        return{
            jobself:this.job
        }
    },
    mounted () {
        //原型鏈繼承
        var that=this;
        this.bus.$on("changeJob",function(name){
             console.log("點擊後傳過來的job",name)
             that.jobself=name;
        })
    }
}
</script>

<style>
</style>

HelloWorld頁面無需更改,最終效果圖如下:

 

 

3、Vuex

接下來介紹今天的主角 Vuex ,Vuex 說白了就像是開闢了一個組件共有的內存空間,組件都可以去取去用;

我們在src創建 store 文件夾,裏面添加一個 index.js 來存儲共享數據,在 main.js 中引入 store文件,並在 Vue 實例中注入 store 。

main.js

import Vue from 'vue'
import App from './App'
import router from './router'

import Vuex from 'vuex'
import store from './store'

Vue.use(VueRouter)
Vue.use(Vuex)

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

JobList.vue

<template>
     <div>
         <button v-for="item of jobs" :key="item.id" @click="jobClick(item.name)">{{item.name}}</button>
     </div>
</template>
 
<script>
export default {
    name:'JobList',
    props:{
        jobs:Array,
    },
    data(){
        return{   
        }
    },
    methods:{
        //vuex
        jobClick(name){
            this.$store.dispatch("changeJob",name)
        },
    }
}
</script>

<style>
</style>

在 JobList.vue 中添加 jobClick 點擊方法,根據上面說的流程將一個 changJob 事件通過 dispatch 方法注入,並將所選的 job 值傳入。

倉庫 store 包含了應用的數據(狀態)和操作過程。 Vuex 裏的數據都是響應式的,任何組件使用同一 store 的數據時,只要 store 的數據變化,對應的組件也會立即更新。

mutation 裏不應該異步操作數據,所以有了 actions 選項 action與mutation很像,不同的是 action 裏面提交的是 mutation ,並且可以異步操作業務邏輯。action 在組件 通過$store.dispatch 觸發。

 

store文件夾下的index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
     state: {
        job:"web"
     },
     actions: {
        changeJob(ctx,name){
            console.log("action",ctx,name);
            ctx.commit("changeJob",name);
        }
     },
     mutations: {
         changeJob(state,name){
             console.log("mutation",state,name);
             state.job=name;
         }
     }
})

JobTop.vue

獲取到job的值,渲染出來

<template>
     <p>{{this.$store.state.job}}</p>
</template>
 
<script>
export default {
    name:'JobTop',
    props:{
        job:String,
    },
    data(){
        return{
        }
    },
}
</script>

<style>
</style>

HelloWorld頁面不需要改動,頁面效果如下:

 

 

或者將store/index.js改爲:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
     state: {
        job:"web",
        list: [10,18,30,45]
     },
     getters: {
        filteredList: state =>{
            return state.list.filter(item => item<30);
        }
     },
     actions: {
        // changeJob(ctx,name){
        //     console.log("action",ctx,name);
        //     ctx.commit("changeJob",name);
        // },
        // 獲取
        changeJob({state, commit}, params) {
            console.log("....."+state.job)   //沒有commit時,是上一次的值
            console.log("=====>"+params);
            commit("changeJob", params);
            console.log("-----"+state.job)           
        }
     },
     mutations: {
         changeJob(state,name){
             console.log("mutation",state,name);
             state.job=name;
         }
     }
})

結果:

注意commit 前後對 state.job 的影響:點python,點java,再點java

 

4、重構

以上就是vue的幾種組件傳參方式,大家想了解的更加官方,請閱覽官方地址:https://vuex.vuejs.org/zh/

根據官方 Vuex 目錄,我們再深一步講解一下核心概念,對上面的代碼進行一下重構。

JobList.vue

<template>
     <div>
         <button v-for="item of jobs" :key="item.id" @click="jobClick(item.name)">{{item.name}}</button>
     </div>
</template>
 
<script>
import { mapMutations } from 'vuex'
export default {
    name:'JobList',
    props:{
        jobs:Array,
    },
    data(){
        return{  
        }
    },
    methods:{
        //vuex
        jobClick(name){
            this.changeJob(name)
        },
        ...mapMutations(['changeJob'])
    }
}
</script>

<style>
</style>

尤大大爲我們提供了封裝好的一些方法,如上面的 ...mapMutations() ,意思是 mutations 裏有一個 changeJob ,我們將其映射到該組件的一個 changeJob的方法裏,這樣就可以直接調用 changeJob 方法並傳值了

JobTop.vue

<template>   
      <p>{{this.job}}</p>
</template>
 
<script>
import { mapState } from 'vuex'
export default {
    name:'JobTop',
    props:{
    },
    data(){
        return{
        }
    },
    computed:{
       ...mapState(['job'])
    },
}
</script>

<style>
</style>

在 mapState 裏有一個 city 屬性,將該屬性映射到該組件的 city 屬性裏,

 效果圖如下:

 

5、getters用法

Vuex 定義了某個數據 list,它是一個數組,如果只想得到小於30 的數據,最容易想到的方法可能是在組件的計算屬性裏進行過濾。

JobTop.vue

<template>
     <div>{{list}}</div>
</template>
 
<script>
export default {
    name:'JobTop',
    props:{
        job:String,
    },
    data(){
        return{
            
        }
    },
    computed:{
        list(){
            return this.$store.state.list.filter(item => item<30)
        }
    }
}
</script>

<style>
</style>

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
     state: {
        job:"web",
        list: [10,18,30,45]
     }
})

結果:

[ 10, 18 ]

 

這樣寫完全沒有問題。但如果還有其他的組件也需要過濾後的數據時,就得 computed 的代碼完全複製一份,而且需要修改過濾方法時,每個用到的組件都得修改,這明顯不是我們期望的結果。如果能將 computed的方法也提取出來就方便多了, getters就是來做這件事的。
使用 getters 改寫上面的示例:

JobTop.vue

<template>
     <div>{{list}}</div>
</template>
 
<script>
export default {
    name:'JobTop',
    props:{
        job:String,
    },
    data(){
        return{
            
        }
    },
    computed:{
        list(){
           return this.$store.getters.filteredList;
        }
    }
}
</script>

<style>
</style>

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
     state: {
        job:"web",
        list: [10,18,30,45]
     },
      getters: {
         filteredList: state =>{
             return state.list.filter(item => item<30);
         }
     }
})

結果:

[ 10, 18 ]

 

6、modules用法

它用來將 store 分割到不同模塊。當你的項目足夠大時, store 裏的 state、getters、utations、actions 會非常多,都放在 main.js 裏顯得不是很友好,使用 modules 可以把它們寫到不同的文件中。每個 module 擁有自己的 state、getters、mutations、actions ,而且可以 多層嵌套。


比如下面的示例:

const moduleA = { 
    state: { . . . } , 
    mutations: { . . . } , 
    actions: { . . . ) , 
    getters: { . . . )
}

const moduleB= { 
    state: { . . . } , 
    mutations: { . . . } , 
    actions: { . . . ) , 
    getters: { . . . )
}


const store= new Vuex.Store ( { 
    modules : { 
        a: moduleA, 
        b: moduleB
    }
}) 
store.state.a // moduleA 的狀態
store.state.b // moduleB 的狀態

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const moduleA = {
    state: {
        job:"web",
        list: [10,18,30,45]
     },
     getters: {
        filteredList: state =>{
            return state.list.filter(item => item<30);
        }
     },
}

export default new Vuex.Store({
     modules : { 
        a: moduleA,
     }
})

JobTop.vue

這裏不需要修改:

computed:{
        list(){
           return this.$store.getters.filteredList;
        }
}

參考:https://blog.csdn.net/weixin_44811417/article/details/90766421

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