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

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

       一、單文件組件html

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

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

二、動態組件&異步加載jquery

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

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

 

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

      我想到了秦腔中的「變臉」,對,這個頗有意思,一我的經過變臉能夠扮演多我的。和演員同樣,好比最近的一個電視劇中景甜扮演了「奉劍」和「千湄」兩個角色。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,要呈現哪一個組件,交給調用方,誰調用我,誰就必須告訴我,該顯示哪一個子組件。

三、組件通訊

組件間通訊問題,是一個廣泛問題。組件再獨立也得和其它組件協同完成任務吧。沒有一個組件能完成全部事情常見的那就父子之間的通訊以及兄弟之間的通訊
。有沒有父組件引起了一個事件,由子組件來處理呢?貌似沒有。若是有的話,就是父組件更改了Props中屬性的值。若是子組件非要在更改值
的時候,做出某些處理的話,那麼就用Watch了。
 
 
  props: ["pageIndex", "pageSize", "total", "groups", "skin"],
watch: {
          total(val, oldVal) {
              if (val != oldVal) {
                 this.render();
              }
          },
      }

   這個watch監視的是total(總頁數),是分頁組件監視Props中的total,一旦total改變,那麼分頁組件須要render,調用render方法從新渲染本身。dom

   子組件觸發事件,父組件監聽,這是很是常見的。好比彈出框組件中的關閉事件,分頁組件中的 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是一個集中式的狀態管理器。「天下有變,則命一上將將荊州之軍以向宛、洛。。。。。。」,《隆中》反映了蜀漢對天下大勢要密切監視,一旦

發生了變化,就要採起行動了。兄弟之間的通訊,咱們項目還真沒有用到過,若是須要的同窗,可進一步查閱資料,這裏僅探討思路。

四、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>

五、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對象中,並且獲得執行。咱們作一件事情,要看準時機,若是時機不對,事倍功半,甚至一敗塗地。諸葛亮出山的時機不對啊。

六、實例變量 && $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組件開發的一些問題。

相關文章
相關標籤/搜索