vue前端開發那些事——vue組件開發

  vue的學習曲線不是很陡(相比其它框架,如anglarjs),官方文檔比較全面,分爲基礎篇和高級篇。我們剛開始學習的時候,肯定像引用jquery那樣,先把vue的js引進來,然後學習基礎內容。如果僅僅停留在基礎內容,沒有學習vue組件的話,我覺得也就沒有什麼意思了。vue的核心思想——組件,是一個很好的東西,它提供了功能複用。

       1、單文件組件

        所謂單文件組件,顧名思義,一個vue格式的文件就是一個組件。好比python和js的模塊,文件即模塊。vue組件帶有自己的模板,可以理解爲視圖,也帶有自己數據及邏輯。數據可以從外部來,通過Prop接收。用圖形表示:

    由此可見,單文件組件就是一個完整而獨立的體系。注意,style有個屬性scope表示僅作用於當前組件。wpf當中的控件,有自己的xaml(視圖),邏輯,可以外部綁定數據源。我覺得vue組件類似wpf中的用戶控件。因爲用戶控件組合了基礎的控件,比如Button、TextBlock等等。用戶控件可直接當成一個獨立的組件使用。它和vue組件一樣,都是從外部獲取特定的信息,然後構建自己內部的數據以及邏輯。其實組件體現的是面向對象中的“封裝”思想。

2、動態組件&異步加載

     有時候讀了官方的文檔,還是不明白,這時候就需要上網搜搜相關資料。好比《聖經》或者《道德經》中的經文是需要慢慢揣摩和體會的。當然vue的動態組件、以及組件的異步加載也是需要在實踐當中慢慢體會的。下來分享一個我在項目中使用的例子:

在後臺管理頁面中,編輯、增加一條信息,這時候需要在彈出頁面中操作。因此,我就封裝了一個彈出模態框(帶有遮罩效果)的組件。而編輯頁面是另外的一個組件。所以,需要把編輯頁面的組件“送到”彈出框的組件中呈現。有點像“裝飾器模式”,不要原生態地呈現編輯組件,而是把它包裝一番,再呈現,如下圖:

 

      後臺管理中像這樣的編輯頁面非常多,所以彈出框組件的意義就在這,複用。我上面說了需要把“編輯軟件資源”的組件,送到彈出框組件顯示。如何送呢?其實也不難。我把這個組件作爲彈框組件的子組件。那麼這個彈框組件有很多個編輯組件。現在問題來了,如何控制它們顯示?當我點擊“編輯軟件資源”的時候,彈出對應的頁面,當我點擊編輯新聞的時候,它要彈出新聞的頁面,難道我要控制組件的顯示隱藏嗎?這個情況有點像“Tab”,任何時候,只能呈現一個TabItem,那麼其它的只能隱藏掉。好了,我們也可以這麼做。還有另外一個問題,我們如何導入這些組件,一次性import多個組件,貌似也沒有什麼問題。這會不會影響頁面加載的性能呢?我想肯定會。

      我想到了秦腔中的“變臉”,對,這個很有意思,一個人通過變臉可以扮演多個人。和演員一樣,比如最近的一個電視劇中景甜扮演了“奉劍”和“千湄”兩個角色。vue裏面的動態組件就是如此,一個組件總是“扮演”各種組件。異步加載,當我需要用你的時候,再去import,這顯然是合理的。說了這麼多,我們看看代碼:

  1 <template>
  2 <transition name="modal">
  3     <div class="modal-mask">
  4         <div class="modal-wrapper">
  5             <div class="modal-container" :style="{width:width,height:height}">
  6 
  7                 <div class="modal-header">
  8                     <slot name="header">
  9                         {{title}}
 10                     </slot>
 11                     <button class="modal-default-button" @click="close">
 12                         X
 13                     </button>
 14                 </div>
 15 
 16                 <div class="modal-body" :style="{height:bodyHeight,width:bodyWidth}">
 17                     <slot name="body">
 18                         <component :is="currentComponent" @close="close" :id="id"></component>
 19                     </slot>
 20                 </div>
 21 
 22             </div>
 23         </div>
 24     </div>
 25 </transition>
 26 </template>
 27 
 28 <style scoped>
 29 .modal-mask {
 30     position: fixed;
 31     z-index: 9998;
 32     top: 50%;
 33     left: 50%;
 34     width: 100%;
 35     height: 100%;
 36     background-color: rgba(0, 0, 0, .5);
 37     display: table;
 38     transform: translateX(-50%) translateY(-50%);
 39     transition: opacity .3s ease;
 40 }
 41 
 42 .modal-wrapper {
 43     display: table-cell;
 44     vertical-align: middle;
 45 }
 46 
 47 .modal-container {
 48     margin: 0px auto;
 49     padding: 20px 30px;
 50     background-color: #fff;
 51     border-radius: 2px;
 52     box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
 53     transition: all .3s ease;
 54     font-family: Helvetica, Arial, sans-serif;
 55 }
 56 
 57 .modal-header h3 {
 58     margin-top: 0;
 59     color: #42b983;
 60 }
 61 
 62 .modal-body {
 63     margin: 10px 0;
 64     overflow-y: auto
 65 }
 66 
 67 .modal-default-button {
 68     float: right;
 69     background: none;
 70     border: none;
 71     cursor: pointer;
 72 }
 73 
 74 /*
 75  * The following styles are auto-applied to elements with
 76  * transition="modal" when their visibility is toggled
 77  * by Vue.js.
 78  *
 79  * You can easily play with the modal transition by editing
 80  * these styles.
 81  */
 82 
 83 .modal-enter {
 84     opacity: 0;
 85 }
 86 
 87 .modal-leave-active {
 88     opacity: 0;
 89 }
 90 
 91 .modal-enter .modal-container,
 92 .modal-leave-active .modal-container {
 93     -webkit-transform: scale(1.1);
 94     transform: scale(1.1);
 95 }
 96 </style>
 97 
 98 <script>
 99 export default {
100     props: {
101         title: {
102             type: String
103         },
104         width: {
105             type: String,
106             required: false,
107             default: '30%'
108         },
109         height: {
110             type: String,
111             required: false,
112             default: '65%'
113         },
114 
115         currentComponent: {
116             type: String,
117             required: true
118         },
119         id: {
120             type: Number,
121             default: 0
122         }
123     },
124     components: {
125         newsItem(resolve) {
126             require(['../Admin/newsItem'], resolve)
127         },
128         softwareItem(resolve) {
129             require(['../Admin/softwareItem'], resolve)
130         }
131     },
132     data() {
133         return {
134             bodyHeight: '98%',
135             bodyWidth: '100%'
136         }
137     },
138     methods: {
139         close(type) {
140             if (type)
141                 this.$emit('close', type);
142             else
143                 this.$emit('close');
144         }
145     }
146 }
147 </script>

Props中接收 currentComponent,要呈現哪個組件,交給調用方,誰調用我,誰就必須告訴我,該顯示哪個子組件。

3、組件通信

組件間通信問題,是一個普遍問題。組件再獨立也得和其它組件協同完成任務吧。沒有一個組件能完成所有事情常見的那就父子之間的通信以及兄弟之間的通信
。有沒有父組件引發了一個事件,由子組件來處理呢?貌似沒有。如果有的話,就是父組件更改了Props中屬性的值。如果子組件非要在更改值
的時候,作出某些處理的話,那麼就用Watch了。

  props: ["pageIndex", "pageSize", "total", "groups", "skin"],
watch: {
          total(val, oldVal) {
              if (val != oldVal) {
                 this.render();
              }
          },
      }

   這個watch監視的是total(總頁數),是分頁組件監視Props中的total,一旦total改變,那麼分頁組件需要render,調用render方法重新渲染自己。

   子組件觸發事件,父組件監聽,這是非常常見的。比如彈出框組件中的關閉事件,分頁組件中的 pageHandler 分頁事件,這些都要父組件來處理,子組件通過 $emit,這是vue全局的方法,哪個組件都可以用。父組件必須監聽pageHandler事件:

 <ym-pager v-if="total" :page-index="pageIndex" :page-size='pageSize' :total='total' :groups="5" @pageHandler="loadData"></ym-pager>

兄弟之間的通信,如何解決呢?網上一搜,基本上都是給一個總線級別的組件,這個組件就是用來通訊的,誰需要發佈事件,就往這裏發,誰需要處理,那麼就監聽相關事件。理論上可以實現,但是我在實踐的過程中,始終沒有成功,不知道爲什麼。還有一種思路,

通過vuex實現,事件發佈方,更改vuex中的某個狀態值,那麼監控方發現這個狀態有變化的時候,就去處理事件。vuex是一個集中式的狀態管理器。“天下有變,則命一上將將荊州之軍以向宛、洛。。。。。。”,《隆中》反映了蜀漢對天下大勢要密切監視,一旦

發生了變化,就要採取行動了。兄弟之間的通信,我們項目還真沒有用到過,如果需要的同學,可進一步查閱資料,這裏僅探討思路。

4、slot

   這個特別有用,也有意思。插槽,它反映了一種IOC(控制反轉)的思想。本來子組件的呈現由自己做決定,可是某些情況下,子組件的某一部分變數很大,需要抽象出來,就用了slot先佔着,等父組件調用的時候,再告訴該如何渲染。比如我們有一個table組件,這個組件實現了分頁等功能。可是table的表頭和表的內容充滿着變數,若是由父組件通過Props傳遞,也可以,就是特別麻煩,傳遞的東西太多了,而且子組件這邊也需要很多處理。大道至簡,用slot,簡潔。table組件不用那麼費勁。調用table的父組件也不用想着如何更好地傳遞數據了。

  <table class="ym-table table-hover">
         <slot name="thead"></slot>
         <slot name="tbody"></slot>
  </table>

table組件中定義了兩個命名slot,看看如何調用:

  <YmTable :page-title="pageTitle" :total="totalCount" :page-size="pageSize" @pager="pager" @newItem="newItem">
        <thead slot="thead">
            <tr>
                <th>序號</th>
                <th>軟件名稱</th>
                <th>簡介</th>
            </tr>
        </thead>
        <tbody slot="tbody">
            <tr v-for="(item,index) in items" :key="item.id">
                <td v-text="getIndex(index)"></td>
                <td>
                    {{item.name}}
                </td>
                <td>
                    {{item.summary}}
                </td>
            </tr>
        </tbody>

    </YmTable>

5、vue生命週期

     生命週期是個老生常談的問題。是個對象,那就總有個生命週期吧。比如.net中Page對象,頁面的生命週期,而且這個還是主考官特別愛考的問題。Android的中Activity的生命週期Page和Activity對象的功能有點像,提供用戶操作的界面,可以簡單地理解爲UI。

網上最著名的就是這張圖:

這個圖,我們大致理解一下,它核心就是如何把VM(虛擬的dom)轉換爲實際dom,而且在什麼時候轉換。這裏有一點記住就行了,Created的時候,dom還沒有被渲染出來,此時不宜操作dom相關的事情。Mounted的時候,做的事情就多了。比如,在mounted的時候,通過layui綁定form的提交事件。

 mounted() {
        let that = this;

        var form = layui.form;

        //綁定form提交事件

        layui.form.on('submit(*)', function (data) {

            that.summit();

            return false;
        });
    },

再例如,封裝了一個Select的組件,在updated的時候,執行select的render:

  updated() {
        layui.form.render('select');
    },

總之,vue生命週期中,都會留有鉤子函數,通過這些才能把我們的業務邏輯注入到Vue對象中,而且得到執行。我們做一件事情,要看準時機,如果時機不對,事倍功半,甚至一敗塗地。諸葛亮出山的時機不對啊。

6、實例變量 && $nextTick

文檔中是這麼說的:將回調延遲到下次DOM更新循環之後執行。在修改數據之後立即使用這個方法,獲取更新後的DOM。很抽象啊,不理解。但是我需要它。我封裝了一個YmRichText組件,這個組件裏是調用了kindeditor,富文本框。
mounted() {
        let that = this;
        this.$nextTick(function () {
            that.kedit('textarea[name="content"]');
        });

    },
    methods: {
        getConent() {
            return editor.html();
        },
        kedit(k) {
            let that = this;
            window.editor = KindEditor.create(k, {
                width: '98%',
                height: that.height + 'px',
                uploadJson: that.uploadFileUrl,
                allowFileManager: false
            });

        }

    }

當在mounted的時候,不管怎麼樣創建的editor對象都爲空。所以使用了$nextTick。按理說,不應該啊,模板中有textarea,kindeditor的js和css也加載上了,而且也在mounted的時候調用的。但是反過來想,在$nextTick調用成功,說明在當前週期內,是不會調用kindeditor的方法的。我們的分頁組件中,也使用了 $nextTick這個倒好理解,因爲在created時候,調用render,render方法中會操作dom,所以只能等下一個週期執行了。

    created() {
        this.render();
    },
    watch: {
        total(val, oldVal) {
            if (val != oldVal) {
                this.render();
            }
        },
        pageIndex(val) {
            this.cindex = val;
            if (val == 1) {
                this.render();
            }
        }
    },
    methods: {
        render() {
            let self = this;
            this.$nextTick(function () {
                layui.laypage.render({
                    elem: self.pagerId,
                    skin: self.cskin,
                    count: self.total, //總數數
                    limit: self.pageSize, //每頁顯示條數
                    groups: self.cgroups, //連續顯示分頁數
                    curr: this.cindex, //當前頁
                    jump: function (obj, first) {
                        if (!first) self.$emit("pageHandler", obj.curr);
                    }
                });
            });
        }
    }

實例變量多了,比如引用父組件的$parent,引用子組件的$refs,$refs特別有用,比如要執行子組件裏的方法或者獲取子組件的數據。

 <ym-company-select :oldCompanyId="oldCompanyId" ref="company"></ym-company-select>
this.data.companyId = this.$refs.company.companyId;

以上,就是我探討的vue組件開發的一些問題。

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