跳到主要內容

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

留言

這個網誌中的熱門文章

PHP與Python搭配

今天介紹如何利用php網頁呼叫目錄下的python程式工作或是資料交換,本人整理的方法有兩種 使用system()、exec()、shell_exec()呼叫程式 (1) string system ( string return_var ] ) 參考網址 官網解釋system()用來執行外部命令,返回為印出的結果,passthru()跟system()類似但是它不會返回結果。 範例1. 利用system執行ls指定並顯示在網頁上,無法使用變數保留ls的結果 檔案名稱: psystem.php $jsondata= system("ls -al", $result); 結果: (2) exec() : string exec ( string output [, int &$return_var ]] ) 參考網址 範例2. 利用exec執行python程式並可以回傳json格式給前端網頁做處理並顯示。我們ptopy.php就是可以看到callpy()為執行py的函式,它執行完pyEx01.py會將結果給$jsondata變數,做後面json解析。 檔案名稱: ptopy.php function callpy() { $jsondata= exec("/usr/bin/python pyEx01.py"); return $jsondata ; } $jsondata= callpy(); echo $jsondata ; echo " " ; $obj = json_decode($jsondata) ; echo "name:".$obj-> { 'name'} .',' ; echo "id:".$obj-> { 'id'} ; 檔案名稱: pyEx01.py import sys

GSON基礎教學

GSON 前言 JSON是很常見的資料交換格式,在JAVA領域常用處理JSON的函式庫:GSON、FastXML和JSON-B,本章節會以GSON為主,學習目標如下 JSON格式說明 GSON 套件函式 GSON: 物件轉換JSON字串 GSON: JSON字串轉換物件 JSON 格式說明 JSON全名為JavaScript Object Notation,它是一種輕量級的資料交換格式,會大為流行的理由,主要是他比傳統用xml更輕巧且容易處理, JSON表達方式物件會用大括弧{},陣列則是用中括號[]。 用JSON字串來表達Employee的物件內容,由JSON字串可以知道物件name、age、sex和salary屬性。 JSON表示員工資料方式: {“name”:”Jack Bryant”, “age”:18, “sex”:”M”,”salary”:3500.00} JSON陣列表示方式: 跟我們使用JAVA的陣列方式類似,內容值可以是數字’、文字、布林、陣列、物件、null等等。 範例: 字串: [“紅”、”橙”、”黃”、”綠”、”青”、”藍”、”紫”} 布林: [true, true, false, false, true, true] GSON 套件函式 Gson為google所發布的函式庫,主要將物件與json字串之間的轉換時方便使用。當我們將JAVA物件轉換成JSON字串稱為 序列化 ,JSON字串轉換至JAVA物件稱為 反序列化 。 GSON: 物件轉換JSON字串 有了JSON基本概念後,我們進入本章重點,首先我們需要建立員工類別(Employee),定義如下 物件 屬性 員工類別 Employee name 名字 age 年紀 sex 性別 salary 薪水 /** * name:員工類別 */ public class Employee implements Serializable { //constructor public Employee(String name, double salary){ this.name = name; this.sala

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