Vue元件實戰

juexingzhe發表於2017-12-25

最近看了下Vue元件的相關知識,除了官網也推薦這篇部落格www.cnblogs.com/keepfool/p/…,但是這篇部落格中用的是v1.0.25,我就用最新的v2.4.4來模仿下文中的例子,也一起談一談下面幾個方面:

  1. 元件編譯作用域
  2. 父子元件傳遞資料
  3. 非父子元件通訊
  4. 過濾器

先看下最終的實現效果:

vue_component.gif

1.整體程式碼

先看下整體程式碼,首先是html部分, 分成三部分

1.id="app"的container部分 2.id="grid-template"的表格部分 3.id="dialog-template"的對話方塊部分 總共涉及兩個自定義元件,一個父元件<grid-template></grid-template>;一個子元件<modal-dialog></<modal-dialog>.

<body>
    <!-- container -->
    <div id="app">
        <div class="container">
            <div class="form-group">
                <label>
                    Search
                </label>
                <input type="text" v-model="searchQuery" />
            </div>
        </div>
        <div class="container">
            <grid-template :column="columns" :search-key="searchQuery" :data-list="people"></grid-template>
        </div>
    </div>

    <!-- 表格 -->
    <template id="grid-template">
        <div>
            <table>
                <thead>
                    <th v-for="headName in column">
                        {{headName.name | capitalize}}
                    </th>
                    <th>
                        Delete
                    </th>
                </thead>
                <tbody class="text-center">
                    <tr v-for="(entry, index) in filt(dataList, searchKey)">
                        <td v-for="col in column">
                            <span v-if="col.isKey">
                                <a href="javascript:void(0)" @click="openEditItemDialog(index, 'Edit item ' + entry[col.name])">{{entry[col.name]}}</a>
                            </span>
                            <span v-else>{{entry[col.name]}}</span>
                        </td>
                        <td>
                            <button @click="deleteItem(index)">Delete</button>
                        </td>
                    </tr>
                </tbody>
            </table>

            <div class="container">
                <button class="button" v-on:click="openNewItemDialog('Create new item')">Create</button>
            </div>
            <modal-dialog :show="show" :mode="mode" :title="title" :fields="column" :item="item" @on-show-change="handleShow" @add-item="addData"
                @update-item="updateData"></modal-dialog>
        </div>
    </template>

    <!-- 對話方塊 -->
    <template id="dialog-template">
        <div class="dialogs">
            <div class="dialog" v-bind:class="{'dialog-active': myShow}">
                <div class="dialog-content">
                    <header class="dialog-header">
                        <h1 class="dialog-title">{{title}}</h1>
                    </header>

                    <div v-for="field in fields" class="form-group">
                        <label>{{field.name}}</label>
                        <select v-if="field.dataSource" v-model="item[field.name]" :disabled="mode === 2 && field.isKey">
                            <option v-for="opt in field.dataSource" :value="opt">
                                {{opt}}
                            </option>
                        </select>
                        <input v-else type="text" v-model="item[field.name]" :disabled="mode === 2 && field.isKey" />
                    </div>

                    <footer class="dialog-footer">
                        <div class="form-group">
                            <label></label>
                            <button v-on:click="save">Save</button>
                            <button v-on:click="close">Close</button>
                        </div>
                    </footer>
                </div>
            </div>
            <div class="dialog-overlay"></div>
        </div>
    </template>
</body>
複製程式碼

先看下第一部分的程式碼,

    <div id="app">
        <div class="container">
            <div class="form-group">
                <label>
                    Search
                </label>
                <input type="text" v-model="searchQuery" />
            </div>
        </div>
        <div class="container">
            <grid-template :column="columns" :search-key="searchQuery" :data-list="people"></grid-template>
        </div>
    </div>
複製程式碼

上面程式碼中[column, search-key, data-list]都是元件grid-template的屬性,[columns,searchQuery,people ]就是使用container傳給grid-template元件的資料。 定義全域性元件方式Vue.component(name, {}),在父元件grid-template中通過屬性components定義子元件modal-dialog,這也是比較常用的定義全域性元件和區域性元件的方式。

Vue.component('grid-template', {
        template: "#grid-template",
        props: [
            'dataList', 'column', 'searchKey'
        ],        
        components: {
            'modal-dialog': {
                template: '#dialog-template',
                data: function () {
                    return {
                        myShow: this.show
                    }
                },
                props: [
                    'mode',
                    'title',
                    'fields',
                    'item',
                    'show'
                ]
            }
        }
}
複製程式碼

html中用kebab-case (短橫線分隔命名),JS中採用camelCase (駝峰式命名),這個Vue會自動進行識別,我們按各自習慣進行編碼就行。

2.元件編譯作用域

就跟程式語言中的函式有作用域一說,Vue中元件也有作用域,並且作用域只在元件的模板中。 看一個官方的例子:

<child-component>
    {{ message }}
</child-component>
複製程式碼

這裡message用的是父元件(父元件就是使用這個元件的元件)的資料,還是子元件的資料?答案是父元件。 官方對作用域的定義:

父元件模板的內容在父元件作用域內編譯;子元件模板的內容在子元件作用域內編譯。

再看一個例子:

<!-- 無效 -->
<child-component v-show="someChildProperty"></child-component>
複製程式碼

假定someChildProperty是子元件的屬性,上面程式碼就會出問題,因為這裡是父元件的作用域,也就是說someChildProperty應該是父元件裡面定義的資料。如果要繫結子元件作用域內的指令到一個元件的根節點,需要在子元件的模板裡做:

Vue.component('child-component', {
  // 有效,因為是在正確的作用域內
  template: '<div v-show="someChildProperty">Child</div>',
  data: function () {
    return {
      someChildProperty: true
    }
  }
})
複製程式碼

來仔細看下上面程式碼的第二部分-表格部分,看看父元件使用子元件的情況:

    <!-- 表格 -->
    <template id="grid-template">
        <div>
            <modal-dialog :show="show" :mode="mode" :title="title" :fields="column" :item="item" @on-show-change="handleShow" @add-item="addData"
                @update-item="updateData"></modal-dialog>
        </div>
    </template>
複製程式碼

其中,[show, mode, title, fields, item]modal-dialog元件自己定義的屬性,@是v-on:的簡寫,method [handleShow, addData] 是父元件id="grid-template"作用域中的函式

    Vue.component('grid-template', {
        template: "#grid-template",
        methods: {
            handleShow: function (val) {
                this.show = val
            },
            addData: function (item) {
                if (this.itemExists(item)) {
                    alert('Item ' + item[this.keyColumn] + " is already exists");
                    this.show = true;
                    return;
                }
                this.dataList.push(item)
                this.item = {}
                this.show = false
            },
            updateData: function (item) {
                var keyColumn = this.keyColumn

                for (var i = 0; i < this.dataList.length; i++) {
                    if (this.dataList[i][keyColumn] === item[keyColumn]) {
                        for (var j in item) {
                            this.dataList[i][j] = item[j]
                        }
                        break;
                    }
                }

                item = {}
            }
        },
        components: {
            'modal-dialog': {
                template: '#dialog-template',
                ......
            }
        }
    })
複製程式碼

3.父子元件傳遞資料

元件例項的作用域是孤立的。這意味著不能 (也不應該) 在子元件的模板內直接引用父元件的資料。父元件的資料需要通過 prop 才能下發到子元件中。 子元件要顯式地用[props]宣告它預期的資料:

Vue.component('child', {
  // 宣告 props
  props: ['message'],
  // 就像 data 一樣,prop 也可以在模板中使用
  // 同樣也可以在 vm 例項中通過 this.message 來使用
  template: '<span>{{ message }}</span>'
})
複製程式碼

然後我們可以這樣向它傳入一個普通字串: 結果就是 hello!

也可以採用動態props,也就是v-bind指令。v-bind 動態地將 prop 繫結到父元件的資料。每當父元件的資料變化時,該變化也會傳導給子元件:

    <div id="app">
        <div class="container">
            <div class="form-group">
                <label>
                    Search
                </label>
                <input type="text" v-model="searchQuery" />
            </div>
        </div>
        <div class="container">
            <grid-template :column="columns" :search-key="searchQuery" :data-list="people"></grid-template>
        </div>
    </div>
複製程式碼

子元件怎麼和父元件通訊呢?這個時候 Vue 的自定義事件系統就派得上用場了。

使用 $on(eventName) 監聽事件 使用 $emit(eventName) 觸發事件

看看我們這個例子中,對話方塊中的新增了資料需要通知父元件<grid-template></grid-template>,可以這樣操作: 在子元件對話方塊中<modal-dialog></<modal-dialog>

<footer class="dialog-footer">
        <div class="form-group">
               <label></label>
               <button v-on:click="save">Save</button>
         </div>
</footer>

 methods: {
     save: function () {
               this.$emit('add-item', this.item)
     }
},
複製程式碼

Vue監聽到了'add-item事件,Vue就會呼叫父元件`中的addData方法:

<modal-dialog @add-item="addData" ></modal-dialog>

methods: {
            addData: function (item) {
                if (this.itemExists(item)) {
                    alert('Item ' + item[this.keyColumn] + " is already exists");
                    this.show = true;
                    return;
                }
                this.dataList.push(item)
                this.item = {}
                this.show = false
}
複製程式碼

4. 非父子元件通訊

有時候,非父子關係的兩個元件之間也需要通訊。可以使用一個空的 Vue 例項作為事件匯流排:

var bus = new Vue()

// 觸發元件 A 中的事件
bus.$emit('id-selected', 1)

// 在元件 B 建立的鉤子中監聽事件
bus.$on('id-selected', function (id) {
  // ...
})
複製程式碼

在我們這個例子中,對話方塊的顯示需要父子元件進行通訊,點選Create按鈕,需要控制show的true或false,從而控制對話方塊的顯示與否。有的小夥伴可能立馬想到props的方法,但是這樣就會有一個問題:

通過父元件把show的值傳給子元件對話方塊作為屬性,是可以達到控制對話方塊的顯示或隱藏,但是在對話方塊中點選save或者close按鈕時需要改變這個show的值,這時候就相當於子元件改變了父元件傳過來的屬性props了,這是比較不和諧的,Vue推薦props傳到子元件的時候是隻讀的,也就是子元件不要改變父元件傳過來的porps。

    <template id="dialog-template">
        <div class="dialogs">
            <div class="dialog" v-bind:class="{'dialog-active': show}">
                
        </div>
    </template>
複製程式碼

這時候就可以通過事件的方式來達到這個目的,show這個狀態只作為子元件對話方塊自己的data,父元件不知道這個屬性,要控制對話方塊的狀態,需要給子元件傳送事件:

<div class="container">
     <button class="button" v-on:click="openNewItemDialog('Create new item')">Create</button>
</div>

Vue.component('grid-template', {
        template: "#grid-template",
        methods: {
            openNewItemDialog: function (title) {
                this.title = title
                this.mode = 1
                this.item = {}
                bus.$emit('dialog-show', true)    //通過匯流排傳送dialog-show事件
            }
            addData: function (item) {
                this.dataList.push(item)
            },
        },
        components: {
            'modal-dialog': {
                template: '#dialog-template',
                mounted: function () {
                    bus.$on('dialog-show', function (show) {
                        this.show = show
                    }.bind(this))
                }
            }
        }
})
複製程式碼

1.點選Create按鈕,通過bus.$emit('dialog-show', true) //通過匯流排傳送dialog-show事件 2.子元件在mounted生命週期鉤子中監聽事件 mounted: function () { bus.$on('dialog-show', function (show) { this.show = show }.bind(this)) }

如果一定要通過屬性props的方式呢?可以這樣,

    components: {
            'modal-dialog': {
                template: '#dialog-template',
                data: function () {
                    return {
                        myShow: this.show
                    }
                },
                methods: {
                    close: function () {
                        this.myShow= false
                    }
                },
                watch: {
                    show(val) {
                        this.myShow = val;
                    },
                    myShow(val) {
                        this.$emit("on-show-change", val);
                    }
                },
            }
        }    
複製程式碼

1.子元件中不要直接修改父元件傳遞過來的props,會報錯: 在子元件中直接修改props-show close: function () { this.show = false } Error: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. 2.通過計算屬性,在watch中父元件show改變的時候改變子元件的myShow屬性;同理,子元件myShow屬性改變的時候通過emit發射事件通知父元件改變show屬性 <modal-dialog @on-show-change="handleShow" > 在父元件中定義方法 handleShow: function (val) { this.show = val }

5.過濾器

Vue.js 允許你自定義過濾器,可被用於一些常見的文字格式化。過濾器可以用在兩個地方:雙花括號插值和 v-bind 表示式 (後者從 2.1.0+ 開始支援)。過濾器應該被新增在 JavaScript 表示式的尾部,由“管道”符號指示:

<!-- 在雙花括號中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
複製程式碼

有兩種定義方式:在元件選項中通過filters定義本地的過濾器;通過Vue.filter定義全域性的過濾器

// 首字母大寫的過濾器
Vue.filter('capitalize', function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
})
複製程式碼

像下面這樣使用,表格標題首字母大寫

 <th v-for="headName in column">
    {{headName.name | capitalize}}
</th>
複製程式碼

6.總結

本文例子來源於www.cnblogs.com/keepfool/p/…,一方面是自身學習,另外一方面用V2.4.4重寫了,對一些細節進行了擴充套件解釋,希望對大家有點幫助哈,謝謝!

相關文章