# [Vue.js] 元件Component用法 ###### tags: `Vue.js` ### 什麼是元件? Vue 應用程式的使用,主要是以 Vue component 元件 所組成,而最上層是以 Root 為主,下面包含 Header, Content 與 side (不同的 component )。而每個 Component 當中的 data 都會是互相獨立的,使用前端框架的好處在於,很多時候同樣重複的事情,你只需要做一次,就能重複使用,並且在後面的維護上更為方便。 Props:由外向內傳入,特性為資料一更新,則傳入 data ,隨即更新頁面。 Emit:由內向外傳出,特性為事件觸發才更新資料,屬於事件類型。 原先寫法 ``` <body> <div id="app"> <button @click="count++">You clicked me {{ count }} times.</button> </div> </body> ``` ``` var app = new Vue({ el: '#app', data: { count: 0 } }); ``` 載入元件的寫法 ``` <body> <div id="app"> <button-counter></button-counter> <button-counter></button-counter> </div> </body> ``` ``` Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' }) var app = new Vue({ el: '#app' }); ``` ## props使用時機 下方html原資料狀態 ![](https://i.imgur.com/K3iwxIi.png) 下方script(這裡使用x-template) 命名row-component當作html的元件名稱 ![](https://i.imgur.com/l6rKddp.png) 下方是錯誤案例,外部元件的指令沒有溝通管道因此無法更改元件內部的item.name、item.cach、item.icash的值 ![](https://i.imgur.com/j3ZCIqW.png) 下方則增加props:[person]提供一個接口讓外部的資料能傳入內部做使用,例如再person="item",item的資料會傳到person裡,再匯到元件內部,提供給person.name做使用,以顯示正確資料。 ![](https://i.imgur.com/u3vkAWL.png) ## x-template使用時機 若是 template 中的語法太過複雜,會使用 x-template 的方式來改善程式的可讀性。 x-template 的宣告必須在另一個 <script> 元素中,並為其帶上 text/x-template 的類型,然後通過一個 id 將模板引用過去。 ``` <script type="text/x-template" id="rowCompTemp"> <tr> <td>{‌{ person.name }}</td> <td>{‌{ person.cash }}</td> <td>{‌{ person.icash }}</td> </tr> </script> Vue.component('row-comp', { props: ['person'], template: '#rowCompTemp' }) ``` ## 元件使用方式(全域、區域註冊) ・Global 全域註冊 如果有元件共用的需求,我們會使用Vue.component 語法來註冊一個元件,在註冊全域元件時要給予兩個參數,分別為「組件名稱」及「選項物件」,在下方範例中「組件名稱」為 component-layout ,「選項物件」則為其後的內容。 ``` ///// 全域註冊 ///// <div id=”app”> <component-layout></component-layout> </div> <script> Vue.component("component-layout", { template: `<div>{{text}}</div>`, data(){ return{ text:'我是全域註冊' } } }); let vm = new Vue({ el: "#app" }); <script> ``` 不過使用全域註冊的缺點是,不管有沒有使用到這個元件,其元件就一定會載入,因此,使用全域註冊會將原本不需要的組件載入進來,整體而言,會拖慢網頁載入的時間。 ・Local 局部註冊 如同前面所提,考量全域註冊的缺點,某些特定元件就會用區域註冊的方式,註冊在需要使用它的元件之中,同時,它是一個選項物件,可以由 components 這個選項物件屬性載入 Vue 實例 使用。 ``` ///// 局部註冊 ///// <div id=”app”> <component-layout></component-layout> </div> <script> let vm = new Vue({ el: "#app", components:{ 'component-layout' :{ template: `<div>{{text}}</div>`, data(){ return { text:'我是局部註冊' } } } } }); <script> ``` ## 元件中 data 必須是函式 在 Vue 實例 中,data 可以是 物件(Object) 或 函式(Function),但元件的 data 只能是函式(Function)。 原因是 Vue component 元件 是可以重複利用的,而且在正常的情況下,它會使用不同的資料,因此,data是函式(Function)的話,每次註冊組件,則會回傳一個新的物件。 ``` <script> Vue.component("my-component", { template: '<div>{{text}}</div>', data(){ return{ text:'元件中 data 為函式' } } });let vm = new Vue({ el: "#app" });<script> ``` ``` Vue.component(‘counter-component’, { data: function(){ return {counter: 0} }, template: ‘#counter-component’} ) ``` ## is屬性功用 模版的來源有兩種:DOM 模版 (DOM Template) 和 字串模版 (String Template)。在 DOM 模版狀況下,若瀏覽器無法正確渲染 DOM Elements,則 Vue.js 就無法對模版做正確的解析。 component 的:is屬性可動態決定要渲染的模版。is屬性可解決在特定 HTML 標籤包含格式下的問題,像是<table>只能包<tr>而非客製化標籤<my-row>。 ``` <div id="app"> <table> <tr is="example-item"></tr> </table> </div> <script type="text/x-template" id="my_component_with_template"> <tr> <td>A</td> <td>B</td> <td>C</td> </tr> </script> ``` ``` var vm = new Vue({ el: '#app', delimiters: ['${', '}'], components: { 'example-item': { template: '#my_component_with_template' } } }); ``` ## camelCase 小駝峰 vs kebab-case HTML 是不區分大小寫的,而 JavaScript 是嚴格區分大小寫的。因此,若非使用以 JavaScript 產生模版的方式,意即「字串模版 (String Template)」,而是使用 HTML 模版時,屬性名稱必須使用以 dash 分隔的 kebab-case 命名。 例如,在 HTML 中撰寫屬性名稱「user name」如下 (O) <prompt-component :user-name="name"></prompt-component> (X) <prompt-component :userName="name"></prompt-component> 再次強調,這種狀況只有在 HTML 中樣版會出現,字串模版 (String Template)則無此限制。 ## 字面量語法(Literal)vs 動態語法(Dynamic) 由於屬性 id 的值是由 data 的 id 代入,若只是經由屬性傳遞資料,模版不會做任何處理,得到的資料型態是「string」;但若使用 v-bind 屬性綁定(簡寫為:),將來會與 Vue Instance 結合,解析模版會當成 JavaScript 表達式做計算,得到的資料型態是「number」。 Case 1:Literal 這裡的 id 的資料型態是「string」。點擊按鈕後 console 出來的結果是 string。 `<prompt-component id="id"></prompt-component>` Case 2:Dynamic 這裡的 id 的資料型態是「number」。點擊按鈕後 console 出來的結果是 number。 `<prompt-component :id="id"></prompt-component>` ## emit向外層傳遞事件 還記得剛剛曾說過的「props in, events out」嗎? 雖然我們不能在 <button-counter> 裡面控制父層的 sum,但我們可以透過「事件」來讓父層去觸發資料更新。 要觸發事件,自然要先註冊事件。 第一步,在 view 上面加上 @add-sum="sum++" 來註冊我們的自訂事件「add-sum」,在 add-sum 這個事件被觸發的時候,會對父層的做 sum++。 ``` <body> <div id="app"> <h1>Total: {{sum}}</h1> <button-counter @add-sum="sum++"></button-counter> <button-counter @add-sum="sum++"></button-counter> </div> </body> ``` 那麼,在 button-counter 的部分,則是在原本的 click 事件中,加入 $emit('add-sum') 來表示當使用者點擊按鈕的同時,也要跟著發送 add-sum 事件: ``` Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button @click="count++; $emit(\'add-sum\')">You clicked me {{ count }} times.</button>', }) var app = new Vue({ el: '#app', data: { sum: 0 } }); ``` ## slot元件插槽 在<no-slot-component>無論輸入任何值都不會替換 ``` <h2>沒有插槽可替換的狀態</h2> <no-slot-component> <p>替換的文字</p> </no-slot-component> ``` ``` <script type="text/x-template" id="noSlotComponent"> <div class="alert alert-warning"> <h6>我是一個元件</h6> <p> 這沒有插槽。 </p> </div> </script> ``` ### 單個插槽(Single Slot) 父元件內的宿主內容會被放入子元件的 <slot> 中,只有當父元件內的宿主內容為空時,才會顯示子元件中 <slot> 內的備用內容。 ``` <h2>Slot 基礎範例</h2> <single-slot-component> <!-- 如果中間有添加字串,元件裡的slot文字則不會出現 --> <p>使用這段取代原本的 Slot。</p> </single-slot-component> <single-slot-component> <!-- 如果中間無添加的字串,則會顯示slot裡的文字 --> </single-slot-component> ``` ``` <script type="text/x-template" id="singleSlotComponent"> <div class="alert alert-warning"> <h6>我是一個元件</h6> <slot> 如果沒有內容,則會顯示此段落。 </slot> </div> </script> ``` ### 具名插槽(Named Slots) 使用屬性 name 決定配置的內容。沒有 name 的匿名插槽將成為預設插槽,匹配不到的內容會放置在此;若沒有默認插槽,匹配不到的內容會被捨棄。 ``` <h2>具名插槽</h2> <named-slot-component> </named-slot-component> <named-slot-component> <header slot= "header">替換的 Header</header> <template slot="btn">按鈕內容</template> </named-slot-component> ``` ``` <script type="text/x-template" id="namedSlotComponent"> <div class="card-header"> <slot name="header">這段是預設的文字</slot> </div> <div class="card-body"> <a href="#" class="btn btn-primary"> <slot name="btn">spanGo somewhere</slot> </a> </div> </script> ```