Vue+ElementUI從零開始搭建自己的網站(三、組件間的通信)

前面討論了環境的搭建和導航頁面以及路由的配置,今天我們討論下如何開發一個擁有表單和表格功能的頁面。先上開發完的效果圖: 

可以看出頁面非常的簡單,其中上半部分是表單搜索和查詢,下半部分是用於展示數據的表格。如果按照傳統的開發思路,其實非常簡單,只要用兩個div,第一個div放置表單,第二個div放置表格即可。但是,我們今天要介紹的,是這個頁面的另一種寫法,也是vue作爲一個優秀的前端框架的核心功能,也就是組件化的寫法。

什麼是組件化?

某搜索引擎告訴我們,組件化是指解耦複雜系統時將多個功能模塊拆分、重組的過程,有多種屬性、狀態反映其內部特性。以我們這次要編寫的頁面爲例,組件化就是要將這個頁面裏面的表格和表單分開成兩個不同的組件,每個組件有它自己的屬性和狀態,既互不干擾又可以互相通信。

爲什麼要組件化?

從上文中的定義我們也可以看出,組件化的主要目的是解耦。當然,還有其他的目的,比如組件複用,按需引入等。具體的細節我們可以先往下看。

開始之前

爲了規範工程的層級,我們把原先與Navi文件夾同級的Page1,Page2和Page3.vue文件刪掉,重新建立三個名爲Page1,Page2和Page3的文件夾,並分別在三個新建立的文件夾中建立Page1.vue,Page2.vue和Page3.vue。當然,結束之後同樣要修改vue-router對於這三個組件的引用路徑。如下圖 

接着,在剛纔建立的Page1文件夾下,建立兩個新vue組件:StudentForm.vue和StudentTable.vue。 
這兩個組件就是我們即將要編寫的表單組件和表格組件。

接下來要介紹兩種vue組件間傳值的方法。對於父子組件的傳值,網上有很多教程,這裏不詳述。對於其他類型的傳值,我們這裏要介紹vue的狀態管理機制,vuex

我們首先在src目錄下新建一個名爲vuex的文件夾,在vuex文件夾下建立一個index.js文件,作爲vuex的配置文件。然後在vuex文件夾下再建立一個Modules文件夾,用於放置模塊的狀態文件。在Modules中新建一個Navi.js,用於存儲Navi模塊的狀態;新建一個Student.js,用於存儲我們即將要寫的student模塊的狀態。 
下面是代碼 
Navi.js

/*
 * 導航頁
 */
const state = {
    //學生類型
    studentTypeList:[],
}

const actions = {
    //存入交通類型數據
    changeStudentTypeListAction({commit}, payload) {
        commit('changeStudentTypeListMutation', payload)
    },
}

//mutations,真正用來修改state的方法集
const mutations = {
    changeStudentTypeListMutation (state, payload) {
        state.studentTypeList = payload
    },
}

const getter = {

}

const moduleNavi = {
    state: state,
    mutations: mutations,
    actions: actions,
    getter: getter
}

export default moduleNavi;

可以看到我們導出的模塊主要有四個部分:state,mutation,action和getter。state用於存儲模塊的狀態,這個“狀態”可以理解爲在組件化開發下當前模塊的全局變量,即需要進行通信的變量。action用於提交mutation,我們可以在action裏進行異步操作。mutation是真正修改狀態的函數。而getter類似於vue中的computed計算屬性,這裏我們用不到,所以暫時不添加內容。 
下面是Student.js

/*
 * 學生基本信息
 */
const state = {
    //查詢學生基本信息的表單
    studentForm: {
        id: '',
        name: '',
        type: '',
    },

    //是否進行查詢
    studentQueryFlag: false,

}

const actions = {
    //存入搜索船舶基本資料form值
    changeStudentFormAction({commit}, payload) {
        commit('changeStudentFormMutation', payload)
    },

    //更改是否搜索標識
    changeStudentQueryFlagAction ({commit}, payload){
        commit('changeStudentQueryFlagMutation', payload)
    },

}

//mutations,真正用來修改state的方法集
const mutations = {
    changeStudentFormMutation (state, payload) {
        state.studentForm = payload
    },

    changeStudentQueryFlagMutation (state, payload) {
        state.studentQueryFlag = payload
    },
}

const getter = {

}

const moduleStudent = {
    state: state,
    mutations: mutations,
    actions: actions,
    getter: getter
}

export default moduleStudent;

這個模塊就是我們即將要編寫的頁面模塊。這裏面的state存儲了兩個變量:一個是查詢所用到的表單,另一個是用於表示是否進行查詢的標識flag。說到這,就不得不提到我們這次組件化開發,預計的程序運行的流程。這裏我們用Page1.vue作爲表單和表格組件的父組件。

  1. 在頁面中表單內輸入數據
  2. 表單組件通過調用student模塊的action->mutation,將表單內的數據同步到state中
  3. 點擊搜索按鈕時,表單組件通過action->mutation,將state中的搜索flag(初始化爲false)置於true
  4. Page1.vue中設置一個局部變量,將這個局部變量computed爲state中的搜索flag
  5. 將步驟4中的局部變量通過父組件->子組件方式傳值至表格組件中
  6. 表格組件中對這個接收到的值進行watch,當且僅當這個值由false變爲true時,以state中的表單數據爲搜索條件,向服務器發送請求,獲取數據並渲染
  7. 最後一步千萬不要忘了,表格組件還要通過調用student模塊的action->mutation,將state中的搜索flag重新置爲false。

    可以看出這些步驟相對於非組件化編程來說很麻煩,但是它很好的解決了解耦的問題:表單組件不需要知道它的搜索請求發給了誰,而表格組件不需要知道是誰發起的搜索請求。如果你熟悉或使用過消息中間件,或是研究過訂閱發佈模式,你可以體會到相同的感覺。舉個例子:我們一般會使用websocket或一些其他方式來進行服務端對客戶端的消息推送。當我們從服務端推送“更新列表”的消息至客戶端時,客戶端的處理函數可以直接修改state中的搜索flag而達到效果,自始至終都與我們編寫的表單組件不產生關係和耦合。

接下來是vuex中的index.js修改後的代碼

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

Vue.use(Vuex)

import navi from './Modules/Navi'
import student from './Modules/Student'

export default new Vuex.Store({
    modules: {
        navi: navi,
        student: student
    }
})

接下來就是表格組件和表單組件,比較簡單。 
首先是表單組件

<template>
    <div style="border-radius:5px;">
        <div style="border:1px solid;background-color:#FFFFFF;box-shadow: 2px 2px 5px #888888;overflow: hidden;border-radius:5px;">
            <div style="background-color:#20A0FF;padding:5px;color:white;">
                學生資料查詢
            </div>
            <br/>

            <el-form ref="form" :model="form" :inline=true label-width="70px" label-position="left" style="margin-left: 5%">
                <el-row :gutter="10">
                    <el-col :xs="24" :sm="7" :md="7" :lg="8">
                        <el-form-item label="名稱" prop="name">
                            <el-input v-model="form.name"></el-input>
                        </el-form-item>
                    </el-col>

                    <el-col :xs="24" :sm="7" :md="7" :lg="8">
                        <el-form-item label="id" prop="id">
                            <el-input v-model="form.id"></el-input>
                        </el-form-item>
                    </el-col>

                    <el-col :xs="24" :sm="7" :md="7" :lg="8">
                        <el-form-item label="種類" prop="type">
                            <el-select v-model="form.type" clearable filterable placeholder="---請選擇---" style="width:175px">
                                <el-option v-for="item in studentTypeList" :value="item.typeId" :label="item.typeName"></el-option>
                            </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>

                <el-form-item style="float:right">
                    <el-button type="primary" @click="resetForm('form')">清空</el-button>
                    <el-button type="primary" @click="submitForm()">查詢</el-button>
                </el-form-item>
            </el-form>
        </div>
    </div>
</template>

<script>
    import { mapActions } from 'vuex'
    export default {
        data () {
            return {
                //提交的表單
                form: {
                    name:'',
                    id:'',
                    type:''
                },
            }
        },

        methods: {
            ...mapActions({
                saveFormVal: 'changeStudentFormAction',
                search: 'changeStudentQueryFlagAction'
            }),

        //重置表單
        resetForm(formName) {
            this.$refs[formName].resetFields();
        },

        //提交表單
        submitForm: function() {
            this.search(true);
        },
    },

    mounted () {
        this.saveFormVal(this.form);
    },

    computed: {
        studentTypeList(){
            return this.$store.state.navi.studentTypeList;
        }
    }
    }
</script>

<style>

</style>

值得說明的是,如果想調用state的action,需要引入mapActions,也就是js代碼中的第一行

import { mapActions } from 'vuex'

並且在methods裏用以下方式調用action

...mapActions({
    saveFormVal: 'changeStudentFormAction',
    search: 'changeStudentQueryFlagAction'
}),

注意…mapActions是固定方式,不要修改。對於函數體裏面的參數,右側是action的名稱,也就是定義在vuex/Modules/XX.js中的action,而左側是action在當前組件中的“引用”名。換句話說,

saveFormVal: 'changeStudentFormAction'

的意思是使saveFormVal和changeStudentFormAction這個action綁定,這樣在當前組件中調用

this.saveFormVal({key: value})

實際上就是調用changeStudentFormAction({key: value})。 
對於多個mapAction,用逗號隔開即可。

下面是表格組件。

<template>
    <div style="box-shadow: 2px 2px 5px #888888;border-radius:5px;">
        <div style="background-color:#20A0FF;padding:5px;color:white;overflow:hidden;border-radius:5px 5px 0 0">
            <span class="demonstration" style="float:left;padding:5px">學生資料</span>
        </div>

        <div style="margin:1%">
            <el-table
                :data="tableData"
                border
                style="width: 100%"
                :default-sort = "{prop: 'name', order: 'descending'}"
            >
                <el-table-column
                    prop="name"
                    label="姓名"
                    align="center"
                    sortable>
                </el-table-column>

                <el-table-column
                    prop="id"
                    label="id"
                    align="center"
                    sortable>
                </el-table-column>

                <el-table-column
                    prop="age"
                    label="年齡"
                    align="center"
                    sortable>
                </el-table-column>

                <el-table-column
                    prop="sex"
                    label="性別"
                    align="center"
                    sortable>
                </el-table-column>

            </el-table>
        </div>

        <div class="block" align="center">
            <el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current-page="currentPage"
                :page-sizes="[10, 20, 30, 40]"
                :page-size="pageSize"
                layout="total, sizes, prev, pager, next, jumper"
                :total="totalNum">
            </el-pagination>
        </div>
    </div>
</template>

<script>
    import { mapActions } from 'vuex'
    export default {
        props:['searchflag'],
        data () {
            return {
                //表格數據
                tableData:[
                    {
                        id: 1,
                        name: '李小明',
                        sex: '男',
                        type: 0,
                        age: 22,
                        math: 97,
                        verbal: 78,
                        specialize: 82
                    },
                    {
                        id: 2,
                        name: '王小紅',
                        sex: '女',
                        type: 0,
                        age: 21,
                        math: 80,
                        verbal: 90,
                        specialize: 84
                    },
                    {
                        id: 3,
                        name: '趙小剛',
                        sex: '男',
                        type: 0,
                        age: 24,
                        math: 94,
                        verbal: 99,
                        specialize: 97
                    },
                    {
                        id: 4,
                        name: '張小芸',
                        sex: '女',
                        type: 0,
                        age: 23,
                        math: 100,
                        verbal: 90,
                        specialize: 85
                    }
                ],

                //詳情頁可見性
                detailDialogVisible: false,

                //被點擊當前船舶信息
                nowShipInfo:'',

                //表格當前頁
                currentPage: 1,

                //表格數據總量
                totalNum: 0,

                //每頁顯示數據數量
                pageSize: 10,
            }
        },

        methods: {
            //加載表格ajax
            loadData(){
                var id = this.$store.state.student.studentForm.id;
                var tabledata = [];
                console.log(id)
                if(id != ''){
                    this.tableData.forEach((item) => {
                        if(item.id == id)
                    tabledata.push(item)
                })
                    this.tableData = tabledata;
                }
                else{
                    this.tableData=[
                        {
                            id: 1,
                            name: '李小明',
                            sex: '男',
                            type: 0,
                            age: 22,
                            math: 97,
                            verbal: 78,
                            specialize: 82
                        },
                        {
                            id: 2,
                            name: '王小紅',
                            sex: '女',
                            type: 0,
                            age: 21,
                            math: 80,
                            verbal: 90,
                            specialize: 84
                        },
                        {
                            id: 3,
                            name: '趙小剛',
                            sex: '男',
                            type: 0,
                            age: 24,
                            math: 94,
                            verbal: 99,
                            specialize: 97
                        },
                        {
                            id: 4,
                            name: '張小芸',
                            sex: '女',
                            type: 0,
                            age: 23,
                            math: 100,
                            verbal: 90,
                            specialize: 85
                        }
                    ]
                }

                this.totalNum = this.tableData.length;
            },

            //每頁顯示數據變更響應
            handleSizeChange(val) {
                this.pageSize = val;
                this.loadData();
            },

            //換頁響應
            handleCurrentChange(val) {
                this.currentPage = val;
                this.loadData();
            },

            ...mapActions({
                search: 'changeStudentQueryFlagAction'
            }),
    },

    mounted () {
        this.loadData();
    },

    watch: {
        searchflag(newval,oldval){
            if(newval){
                this.loadData();
                this.search(false);
            }
        }
    }
    }
</script>

<style>

</style>

接下來修改Page1.vue,修改後的代碼如下

<template>
    <div>
        <div style="border-radius:5px;">
            <StudentForm></StudentForm>
        </div>

        <br/>

        <div style="border:1px solid;margin-top:5px;background-color:#FFFFFF;border-radius:5px">
            <StudentTable :searchflag="search"></StudentTable>
        </div>
    </div>
</template>

<script type="text/ecmascript-6">
    import StudentForm from './StudentForm.vue'
    import StudentTable from './StudentTable.vue'
    export default {
        data () {
            return {

            }
        },

        components: {
            StudentForm: StudentForm,
            StudentTable: StudentTable
        },

        computed: {
            search(){
                return this.$store.state.student.studentQueryFlag;
            }
        }
    }
</script>

<style>

</style>

注意這裏Page1.vue作爲表格組件和表單組件的父組件,涉及到了與子組件傳值的問題。可以看到

<div style="border:1px solid;margin-top:5px;background-color:#FFFFFF;border-radius:5px">
    <StudentTable :searchflag="search"></StudentTable>
</div>

這段代碼中,有一個

:searchflag="search"

這句話的意思是把子組件中的searchflag變量與當前組件中的search變量進行傳值綁定。而當前組件中的search變量又是對於state中的搜索flag的計算屬性,所以可以看出經過state和Page1兩個“中間件”的傳值,表單組件與表格組件進行了通信。 
如果讀者回看上文中的表格組件的代碼,可以看到

props:['searchflag'],

這就是子組件從父組件中接收傳值的方式。

至此我們這一篇文章的開發就結束了。看一下目錄結構: 

 

組件化開發除了可以做到解耦之外,在代碼複用方面也有很大優勢。比如,我們想在多個頁面中都展示同一個表格,那麼直接在其他頁面中用import的方式引入表格組件即可。如果需要複用的組件較多,我們可以在components文件夾下單獨創建一個common文件夾用於存放共用的組件。

 

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