跳到主要內容

Vuex 教學

摘要

Vuex 使用的方式

摘要

專案結構下通常會有多個組件,組件內可能又有組件,組件之間的溝通,通常會用到 emit 和 props,emit 回來,在維護與開發會造成元件之間耦合度太高,本次介紹Vuex框架主要是將資料、處理邏輯集中化處理,視為網站的全域狀態管理,簡化vue元件之間溝通成本,我們主要介紹核心方法:state、getter、mutation、actions,這四個概念,在大型前端系統開發,利用vuex框架可以減少UI之間的資訊傳遞導致程式碼的複雜度。

單個組件的狀態非常好管理,但當遇到多個組件共享state時,單向數據流的簡潔性就很容易被破壞,主要解決下列應用場景,例如:

  • 多個view依賴同一個state
  • 來自不同view的actions需要變更同一個state

vuex使用限制

  • 只有 mutation 可以改變 state,action --> commit --> mutation
  • action: 可以處理非同步的事件,再利用 commit 與 mutation 溝通
  • mutation: 在處理事件是同步
  • vuex應用架構: 開發時需要使用架構
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export const store = new Vuex.Store({

    //共享物件資訊
    state:{
    },
    //物件計算屬性
    getters:{
     
    },
    //用來同步state的方法
    mutations:{
    
    },

    //使用非同步呼叫
    actions:{

    }

});

State(data)

響應式的資料狀態儲存, 資料狀態變化時,有用到的 component 都會即時更新

  • 首先需要安裝vuex的套件以及宣告new一個Store的物件,然後配置初始化的套件
  • store.js 建立初始化的store物件,並且export為常數
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

* store.js
export const store = new Vuex.Store({
    state:{
        registrations :[],
        users:[
            {id:1, name:'Max',   registered: false},
            {id:2, name:'Anna',  registered: false},
            {id:3, name:'Chris', registered: false},
            {id:4, name:'Sven',  registered: false}
        ]
    }
}
  • main.js 引用store檔案中的store物件

import Vue from 'vue'
import App from './App.vue'
import { store } from './store'

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})
  • App.vue 首頁畫面分別建立Registration.vue和Registrations.vue兩個元件

<template>
  <div id="app">
    <app-registration></app-registration>
    <app-registrations></app-registrations>
  </div>
</template>

<script>
  import Registration from './components/Registration.vue';
  import Registrations from './components/Registrations.vue';

export default {
    components: {
        appRegistration: Registration,
        appRegistrations: Registrations
    }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
  • Registration.vue 註冊會員畫面,users()直接呼叫(this.$store.state)物件,就可以直接將目前人員名單。

<template>
    <div id="registration">
        <h3>Register here</h3>
        <hr>
        <div class="row" v-for="user in users">
            <h4>{{user.name}}</h4>
            <button @click="registerUser(user)">Register</button>
        </div>
    
    </div>
</template>
<script>

    export default { 
        computed: {
            users(){
                    return this.$store.state.registrations;
                }
        },
        methods: {
            registerUser(user){
                const date = new Date();
                const user = this.$store.state.registrations.find(user =>{
                    return user.id == userId
                });
                user.registered =true ; 
                const registration = {
                    userId: user.id, 
                    name: user.name, 
                    date: (date.getMonth()+1) + '/' + date.getDate()
                }

                this.$store.state.registrations.push(registration);

            }
        }
    }

</script>
  • Registrations.vue 顯示已經註冊的人員

<template>
    <div id="registrations">
        <div class="summary">
            <h3>Registrations</h3>
            <h5>Total: {{ total }}</h5>
        </div>
        <hr>
        <div class="row" v-for="registration in registrations">
            <h4>{{ registration.name }}</h4>
            <span @click="unregister(registration)">(Unregister)</span>
            <div class="date">{{ registration.date }}</div>
        </div>
    </div>
</template>

<script>
    import { mapGetters } from 'vuex';
    export default {

        methods: {
            unregister(registration) {
                const date = new Date();
                const user = this.$store.state.users.find(user =>{
                    return user.id == userId
                });

                this.$store.state.user.registered =true ; 
                const registration = {
                    userId: user.id, 
                    name: user.name, 
                    date: (date.getMonth()+1) + '/' + date.getDate()
                }

                this.$store.state.registrations.push(registration);
            }
        },
        computed: { 
            registrations(){
                 return this.$store.state.registrations;
             },
            total() {
                 return this.$store.state.registrations.length;
             }
        }
    }
</script>

<style scoped>
    #registrations {
        box-shadow: 1px 1px 2px 1px #ccc;
        margin: 20px;
        padding: 20px;
        display: inline-block;
        width: 300px;
        vertical-align: top;
        text-align: left;
    }

    .summary {
        text-align: center;
    }

    .row h4 {
        display: inline-block;
        width: 30%;
        margin: 0 0 10px 0;
        box-sizing: border-box;
    }

    .row span {
        width: 30%;
        color: red;
        cursor: pointer;
    }

    .row span:hover {
        color: darkred;
    }

    .date {
        display: inline-block;
        width: 38%;
        text-align: right;
        box-sizing: border-box;
    }
</style>

Getter(computed)

  • store.js 將新增一個getters方法,主要對state狀態進行某些處理,並返回需要的結果如註冊會員的人數、沒有註冊的人和已經註冊的人資料。

export const store = new Vuex.Store({
    state:{
        registrations :[],
        users:[
            {id:1, name:'Max',   registered: false},
            {id:2, name:'Anna',  registered: false},
            {id:3, name:'Chris', registered: false},
            {id:4, name:'Sven',  registered: false}
        ]
    },
    getters:{
        unregisteredUsers(state){
           return state.users.filter(user=>{
                return !user.registered;
            });
        },
        registrations(state){
            return state.registrations;
        },
        totalRegistrations(state){
            return state.registrations.length;
        }
    }
})
  • Registrations.vue 將computed改為呼叫getter的方式來實作,搭配mapGetters來map到getter的方法,讓$store.state裡面有關的物件可以封裝成getter來使用。

<template>
    <div id="registrations">
        <div class="summary">
            <h3>Registrations</h3>
            <h5>Total: {{ total }}</h5>
        </div>
        <hr>
        <div class="row" v-for="registration in registrations">
            <h4>{{ registration.name }}</h4>
            <span @click="unregister(registration)">(Unregister)</span>
            <div class="date">{{ registration.date }}</div>
        </div>
    </div>
</template>

<script>
    import { mapGetters } from 'vuex';
    export default {
        methods: {
            unregister(registration) {
                const date = new Date();
                const user = this.$store.state.users.find(user =>{
                    return user.id == userId
                });

                this.$store.state.user.registered =true ; 
                const registration = {
                    userId: user.id, 
                    name: user.name, 
                    date: (date.getMonth()+1) + '/' + date.getDate()
                }

                this.$store.state.registrations.push(registration);
            }
        },
        computed: { 
            ...mapGetters({
                registrations:'registrations',
                total :"totalRegistrations"

            }),
            addition(){

            }

            // 舊寫法
            // registrations(){
            //     return this.$store.getters.registrations;
            // },
            // total() {
            //     return this.$store.getters.totalRegistrations;
            // }
        }
    }
</script>

<style scoped>
    #registrations {
        box-shadow: 1px 1px 2px 1px #ccc;
        margin: 20px;
        padding: 20px;
        display: inline-block;
        width: 300px;
        vertical-align: top;
        text-align: left;
    }

    .summary {
        text-align: center;
    }

    .row h4 {
        display: inline-block;
        width: 30%;
        margin: 0 0 10px 0;
        box-sizing: border-box;
    }

    .row span {
        width: 30%;
        color: red;
        cursor: pointer;
    }

    .row span:hover {
        color: darkred;
    }

    .date {
        display: inline-block;
        width: 38%;
        text-align: right;
        box-sizing: border-box;
    }
</style>


mutations (commit)

  • 用於更改 state 的方法。它們必須是同步的,並且可以直接修改state物件

  • 只處理同步函數:不要在這進行非同步的動作(例如 setTimeout / 打 API 取遠端資料...等)

  • store.js 新增mutations

export const store = new Vuex.Store({
    state:{
        registrations :[],
        users:[
            {id:1, name:'Max',   registered: false},
            {id:2, name:'Anna',  registered: false},
            {id:3, name:'Chris', registered: false},
            {id:4, name:'Sven',  registered: false}
        ]

    },
    getters:{
        unregisteredUsers(state){
           return state.users.filter(user=>{
                return !user.registered;
            });
        },
        registrations(state){
            return state.registrations;
        },
        totalRegistrations(state){
            return state.registrations.length;
        }
    },
    mutations:{
        register(state,userId){
            const date = new Date();
            const user = state.users.find(user =>{
              return user.id == userId
            });
            user.registered =true ; 
            const registration = {
                userId: user.id, 
                name: user.name, 
                date: (date.getMonth()+1) + '/' + date.getDate()
            }

            state.registrations.push(registration);

        },
        unregister(state,userId){
            const user = state.users.find(user =>{
                return user.id ==  userId
            });
            user.registered = false;

            const registration = state.registrations.find(registration =>{
                return registration.userId == userId ; 
            })

            state.registrations.splice(state.registrations.indexOf(registration), 1);


        }
    }

})

Registration.vue : registerUser(user)呼叫$store的mutations方法,重點為外部元件需要同步到$store內部資訊使用mutations方法來封裝。this.$store.commit('register',user.id) 這是其中一種呼叫mutations方法

<template>
    <div id="registration">
        <h3>Register here</h3>
        <hr>
        <div class="row" v-for="user in users">
            <h4>{{user.name}}</h4>
            <button @click="registerUser(user)">Register</button>
        </div>
    
    </div>
</template>
<script>

    export default { 
        computed: {
            users(){
                    return this.$store.getters.unregisteredUsers;
                }
        },
        methods: {
            registerUser(user){
               this.$store.commit('register',user.id) ; 
            }
        }
    }

</script>
<style scoped>
    #registration {
        box-shadow: 1px 1px 2px 1px #ccc;
        margin: 20px;
        padding: 20px;
        display: inline-block;
        width: 300px;
        vertical-align: top;
    }

    .row h4 {
        display: inline-block;
        width: 70%;
        text-align: left;
        margin: 0 0 10px 0;
    }

    button {
        background-color: lightgreen;
        border: none;
        box-shadow: 1px 1px 1px black;
        font-size: inherit;
        text-align: right;
        cursor: pointer;
    }

    button:hover {
        background-color: green;
    }
</style>

Registrations.vue : this.$store.commit({ type:'unregister', userId:registration.userId,}) 這是另外一種呼叫方式


<template>
    <div id="registrations">
        <div class="summary">
            <h3>Registrations</h3>
            <h5>Total: {{ total }}</h5>
        </div>
        <hr>
        <div class="row" v-for="registration in registrations">
            <h4>{{ registration.name }}</h4>
            <span @click="unregister(registration)">(Unregister)</span>
            <div class="date">{{ registration.date }}</div>
        </div>
    </div>
</template>

<script>
    import { mapGetters } from 'vuex';
    export default {

        methods: {
            unregister(registration) {
                this.$store.commit({
                    type:'unregister',
                    userId:registration.userId,
                    
                }) ; 
            }
        },
        computed: { 
            ...mapGetters({
                registrations:'registrations',
                total :"totalRegistrations"

            }),
            addition(){

            }
        }
    }
</script>

<style scoped>
    #registrations {
        box-shadow: 1px 1px 2px 1px #ccc;
        margin: 20px;
        padding: 20px;
        display: inline-block;
        width: 300px;
        vertical-align: top;
        text-align: left;
    }

    .summary {
        text-align: center;
    }

    .row h4 {
        display: inline-block;
        width: 30%;
        margin: 0 0 10px 0;
        box-sizing: border-box;
    }

    .row span {
        width: 30%;
        color: red;
        cursor: pointer;
    }

    .row span:hover {
        color: darkred;
    }

    .date {
        display: inline-block;
        width: 38%;
        text-align: right;
        box-sizing: border-box;
    }
</style>

actions (methods)

  • 用於處理異步操作。它們可以包含任意的異步操作,並最終提交 mutations 來改變store狀態。

  • commit → 呼叫 mutation 改變 state

  • store.js :使用actions方法利用setTimeout非同步的方式來實作註冊方式。

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

Vue.use(Vuex);


export const store = new Vuex.Store({
    state:{
        registrations :[],
        users:[
            {id:1, name:'Max',   registered: false},
            {id:2, name:'Anna',  registered: false},
            {id:3, name:'Chris', registered: false},
            {id:4, name:'Sven',  registered: false}
        ]

    },
    getters:{
        unregisteredUsers(state){
           return state.users.filter(user=>{
                return !user.registered;
            });
        },
        registrations(state){
            return state.registrations;
        },
        totalRegistrations(state){
            return state.registrations.length;
        }
    },
    mutations:{
        register(state,userId){
            const date = new Date();
            const user = state.users.find(user =>{
              return user.id == userId
            });
            user.registered =true ; 
            const registration = {
                userId: user.id, 
                name: user.name, 
                date: (date.getMonth()+1) + '/' + date.getDate()
            }

            state.registrations.push(registration);

        },
        unregister(state,userId){
            const user = state.users.find(user =>{
                return user.id ==  userId
            });
            user.registered = false;

            const registration = state.registrations.find(registration =>{
                return registration.userId == userId ; 
            })

            state.registrations.splice(state.registrations.indexOf(registration), 1);


        },
        actions:{
            register(context, userId){
                setTimeout(()=>{
                    context.commit("register",userId);  // 使用mutations中的register方法
                },10000)
               
            }

        }

    }

})

  • Registration.vue 利用 this.$store.dispatch('register',user.id) 來呼叫actions的方法
<template>
    <div id="registration">
        <h3>Register here</h3>
        <hr>
        <div class="row" v-for="user in users">
            <h4>{{user.name}}</h4>
            <button @click="registerUser(user)">Register</button>
        </div>
    
    </div>
</template>
<script>

    export default { 
        computed: {
            users(){
                    return this.$store.getters.unregisteredUsers;
                }
        },
        methods: {
            registerUser(user){
               this.$store.dispatch('register',user.id);
            }
        }
    }

</script>
<style scoped>
    #registration {
        box-shadow: 1px 1px 2px 1px #ccc;
        margin: 20px;
        padding: 20px;
        display: inline-block;
        width: 300px;
        vertical-align: top;
    }

    .row h4 {
        display: inline-block;
        width: 70%;
        text-align: left;
        margin: 0 0 10px 0;
    }

    button {
        background-color: lightgreen;
        border: none;
        box-shadow: 1px 1px 1px black;
        font-size: inherit;
        text-align: right;
        cursor: pointer;
    }

    button:hover {
        background-color: green;
    }
</style>


總結

當UI元件可以使用async來呼叫actions透過commit來同步更新store物件資料

安裝 Vue Devtools

使用vue Devtools可以方便查看到vue初始化配置況狀、呼叫方法的過程,這是開發者需要必備開發工具。以下連結可以根據不同瀏覽去安裝該套件 https://devtools.vuejs.org/guide/installation.html

留言

這個網誌中的熱門文章

JavaBean 和POJO

前言 今天介紹JavaBean和POJO的不同,這兩個名詞在JAVA文章常常被拿來使用以及討論。在JDK1.1時候釋出才有的一個標準架構,很多時候常常被搞混,所以我們特別開闢一章來加以討論。POJO規範在企業級應用已經廣大的被使用的規範。 解釋 POJO : 全名為Plain-old-Java-object,只需要繼承Object就可以,沒有特定規定,只要建立的類別有setter/getter方法都可以稱為POJO JavaBean: JavaBean通常用來封裝多個物件成為單獨物件使用,規範比較嚴格,規則如下 規則 說明 1 需要實作序列(Serializable/Externalizable) 2 不能有參數的建構子( no-arg constructor) 3 需要有公用setter/getter 4 屬性必須要私人(private) 5 屬於特定POJO規則 比較 所有的JavaBean都為POJO,但是所有的POJO不一定為JavaBean 都可以當作重複元件 都必須序列化 特性都為可用性、易用性和持久化使用 - 應用 由圖我們可以知道POJO在應用程式中,主要用來存取資料庫資料達到持久化的目的,並提供給商業邏輯流程處理使用。這種POJO的架構提供程式人員開發時的可以很有規則將資料封裝並加以使用。 範例1. JavaBean(以員工為實例) JavaBean建立員工物件,可以發現Employee物件建構子沒有任何參數,屬性為私有化並setter/getter的命名方式。 //實作序列化 public class Employee implements java.io.Serializable{ private int id; private String name; //無參數建構子 public Employee(){} //以下實作setter/getter public void setId(int id){this.id=id;} public int getId(){return id;} public void setName(String ...

Python AI-手寫辨識

Python AI-手寫辨識 類神經網路-手寫辨識 手寫辨識 (1) 問題定義 將輸入手寫數字圖片,經由類神經網路訓練後,可以辨識手寫圖片得到一個正確的答案,例如讓電腦辨識上面圖片手寫數字0-9,都可以認得.在了解問題後,需要先知道輸入的資料格式,例如圖片為NxN的矩陣向量. 輸入:輸入的資料格式有很多種,例如數字圖片為矩陣向量 模型:NN 輸出:輸出的方式,神經網路輸出不一定跟輸入同值,手寫數字輸入為1,輸出有可能是1.1或是1.5等等,所以輸出必須經過轉換成真實世界的數字. (2)定義函式 輸出會有兩個問題: A.輸出利用one-hot encoding來表示,就是N個狀態會對應N的結果,例如:輸出結果為1,表示[0,1,0,0,0,0,0,0,0,0] B.輸出結果不能超過1,我們通常會利用 Softmax函数 來進行輸出的處理. (3) 準備訓練/測試資料 在這邊需要從輸入去定義那些要當作訓練與測試資料,我們手寫資料使用MNIST 資料庫來訓練使用,MNIST共有70,000筆手寫資料,60,000筆為訓練資料,10,000為測試資料. (4)建構類神經網路模型 開始建構我們的神經網路模型,首先決定好28x28的像素(這邊不用擔心如何將圖片轉成矩陣),模型使用SGD的方式進行學習,輸出是一個10為的陣列來表示. 輸入:手寫數字圖片(28x28=784) 模型:SGD 輸出:數字(one hard encoding) (5)學習 首先介紹SGD(Stochastic Gradient Descent) 的學習方式,因為蕾神經網路需要訓練很多次才會提高準確度,SGD最大的好處就是當每次重新學習的會將訓練資料打散,來防止機器學習將答案死背下來. (6)實作開發 下面程式碼有完整的說明,這邊就不多說明了,當開始執行程式時就會進行資料訓練. 由訓練結果最後acc=0.9447,表示準確率可以到達94%,我們再由實際測試可以看出該圖為7的圖示,由神經網路判斷為7,跟我們人類判斷相同,我們可以知道由訓練的結果可看得到不錯的準確度. 執行神經網路遇到不少問題,請參考下面連結,是筆者所整理的問題集,請多多指教 https://programdoubledragon.bl...

Python AI-問題集

Python AI-問題集 問題集 Jupyter Notebook執行ipywidgets會出現kernel死掉的錯誤發生(The kernel appears to have died) 解決方法 (1) 根據log檔來判斷問題: 例如:log訊息出現OMP: Error #15: Initializing libiomp5.dylib, but found libiomp5.dylib already initialized. (2) 根據問題關鍵字找出問題所在: 利用google查詢所遭遇到的問題,例如我把上面的問題上google查詢可以找到這篇的解法 https://blog.csdn.net/bingjianIT/article/details/86182096 (3)實作解法: 我實作下面解法後,就可以順利執行手寫辨識的程式. //在Python宣告時加入 import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" 參考 https://blog.csdn.net/bingjianIT/article/details/86182096