1. 程式人生 > 實用技巧 >vue學習(六)元件化開發

vue學習(六)元件化開發

一、註冊元件

1.全域性註冊

利用Vue.component()方法,先傳入一個自定義元件的名字,然後傳入這個元件的配置。

然後就可以在Vue例項掛載的DOM元素中使用它。

 Vue.component('mycomponent',{
    template: `<div>這是一個自定義元件</div>`,
    data () {
      return {
        message: 'hello world'
      }
    }
  })

2.區域性註冊

在某個Vue例項中註冊只有自己能使用的元件。

var app = new Vue({
    el: 
'#app', data: { }, components: { 'my-component': { template: `<div>這是一個區域性的自定義元件,只能在當前Vue例項中使用</div>`, } } })
<div id="app">
    <mycomponent></mycomponent>
    <my-component></my-component>
</div>
<script>
  var
app = new Vue({ el: '#app', data: { }, components: { 'my-component': { template: `<div>這是一個區域性的自定義元件,只能在當前Vue例項中使用</div>`, } } }) </script>

注意:

(1)元件的模板只能有一個根元素下面的情況是不允許的:

template: `<div>這是一個區域性的自定義元件,只能在當前Vue例項中使用</div>
            <button>hello</button>`,

(2)元件中的data必須是函式。

可以看出,註冊元件時傳入的配置和建立Vue例項差不多,但也有不同,其中一個就是data屬性必須是一個函式。
這是因為如果像Vue例項那樣,傳入一個物件,由於JS中物件型別的變數實際上儲存的是物件的引用,所以當存在多個這樣的元件時,會共享資料,導致一個元件中資料的改變會引起其他元件資料的改變。

而使用一個返回物件的函式,每次使用元件都會建立一個新的物件,這樣就不會出現共享資料的問題來了。

(3)DOM模板的解析。

當使用 DOM 作為模版時 (例如,將 el 選項掛載到一個已存在的元素上), 你會受到 HTML 的一些限制,因為 Vue 只有在瀏覽器解析和標準化 HTML 後才能獲取模板內容。尤其像這些元素 <ul><ol><table><select> 限制了能被它包裹的元素,而一些像 <option> 這樣的元素只能出現在某些其它元素內部。

在自定義元件中使用這些受限制的元素時會導致一些問題,例如:

<table>
  <my-row>...</my-row>
</table>

自定義元件 <my-row> 被認為是無效的內容,因此在渲染的時候會導致錯誤。這時應使用特殊的 is 屬性:

<table>
  <tr is="my-row"></tr>
</table>

也就是說,標準HTML中,一些元素中只能放置特定的子元素,另一些元素只能存在於特定的父元素中。比如table中不能放置divtr的父元素不能div等。所以,當使用自定義標籤時,標籤名還是那些標籤的名字,但是可以在標籤的is屬性中填寫自定義元件的名字。

應當注意,如果您使用來自以下來源之一的字串模板,這些限制將不適用:

  • <script type="text/x-template">
  • JavaScript 內聯模版字串
  • .vue 元件

其中,前兩個模板都不是Vue官方推薦的,所以一般情況下,只有單檔案元件.vue可以忽略這種情況。

參考文獻:

https://blog.csdn.net/howgod/article/details/90695332

二、元件的屬性

1.自定義屬性props

Vue元件通過props屬性來宣告一個自己的屬性,然後父元件就可以往裡面傳遞資料。

<!DOCTYPE html>  
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
</head>
<body>
    <div id="app"><child :title="message"></child></div>     //將message傳給title   
    <script>
        Vue.component('child',{  //元件名稱
            template:'<h1>{{title}}</h1>',  //元件可以用到title中massage傳過來的值
            props:['title']       //這裡props是一個字串陣列
        })
        var app = new Vue({
            el:'#app',data:{message:'Hello World'}
        })  
    </script>
</body>
</html>

props除了陣列,也可以是一個物件,此時物件的鍵對應的props的名稱,值又是一個物件,可以包含如下屬性:

type: :型別,可以設定為:String、Number、Boolean、Array、Object、Date等等 ;如果只設置type而未設定其他選項,則值可以直接用型別,例如:props:{title:Object}
default :預設值
required :布林型別,表示是否必填專案
validator :自定義驗證函式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
    <title>Document</title>    
</head>
<body>
    <div id="app"><child></child></div>        
    <script>
        Vue.component('child',{
            template:'<h1>{{title}}</h1>',
            //鍵為title 值為{default:'Hello World'},是一個物件,包含了default屬性
            props:{title:{default:'Hello World'}}   //這裡定義的title是個物件,含有預設值
        })
        var app = new Vue({
            el:'#app'
        })  
    </script>
</body>
</html>

通過v-bind繫結屬性值

v-bind繫結屬性值的一個特性:一般情況下,使用v-bind給元素特性(attribute)傳遞值時,Vue會將""中的內容當做一個表示式。比如:

<div attr="message">hello</div>

上面這樣,div元素的attr特性值就是message

而這樣

<div v-bind:attr="message">hello</div>

這裡的message應該是Vue例項的data的一個屬性,這樣div元素的attr特性值就是message這個屬性的值。

所以如果想傳遞正確的數值,應該使用v-bind傳遞,這樣就能把傳遞的值當做一個表示式來處理,而不是字串。

之所以說是一般情況,是因為classstyle特性並不是這樣。用v-bind:classclass傳入正常的類名,效果是一樣的,因為對於這兩個特性,Vue採用了合併而不是替換的原則。

動態繫結特性值

根據上面,想要把父元件的屬性繫結到子元件,應該使用v-bind,這樣,父元件中資料改變時能反映到子元件。
注意,根據父元件傳遞給子元件的屬性型別的不同,當在子元件中更改這個屬性時,會有以下兩種情況:

(1)當父元件傳遞的屬性是引用型別時,在子元件中更改相應的屬性會導致父元件相應屬性的更改。

 <div id="app2">
     <div>這是父元件的parentArray:{{parentArray}}</div>
     <my-component :child-array="parentArray"></my-component>
   </div>
   <script>
     Vue.component('my-component', {
       template: `
       <div>這是接收了父元件傳遞值的子元件的childArray: {{childArray}} <br>
           <button type="button" @click="changeArray">
           點選我改變父元素的parentArray</button>
         </div>`,
       props: ['childArray'],
       data () {
         return {
           counter: 1
         }
       },
       methods: {
         changeArray () {
           this.childArray.push(this.counter++)
         }
       }
     })
     new Vue({
       el: '#app2',
       data: {
         parentArray: []
       }
     })
   </script>

(2)當父元件傳遞值為基本型別時,在子元件中更改這個屬性會報錯。正確的做法是,在父元件中繫結屬性值時,加上.sync修飾符。

<my-component :child-array.sync="parentArray"></my-component>

然後在子元件中改變相應的屬性

methods: {
     changeArray () {
       this.counter++
       this.$emit('update:childArray', this.counter)
     }
   }

使用$ref實現父子通訊

父子元件通訊參考https://www.jb51.net/article/140581.htm

(1)如果ref用在子元件上,指向的是元件例項,可以理解為對子元件的索引,通過$ref可能獲取到在子元件裡定義的屬性和方法。

(2)如果ref在普通的 DOM 元素上使用,引用指向的就是 DOM 元素,通過$ref可能獲取到該DOM 的屬性集合,輕鬆訪問到DOM元素,作用與JQ選擇器類似。

通過ref實現通訊:

<!-- 父元件 -->
 
<template>
 <div>
 <h1>我是父元件!</h1>
 <child ref="msg"></child>
 </div>
</template>
 
<script>
 import Child from '../components/child.vue'
 export default {
 components: {Child},
 mounted: function () {
  console.log( this.$refs.msg);
  this.$refs.msg.getMessage('我是子元件一!')
 }
 }
</script>
<!-- 子元件 -->
 
<template>
 <h3>{{message}}</h3>
</template>
<script>
 export default {
 data(){
  return{
  message:''
  }
 },
 methods:{
  getMessage(m){
  this.message=m;
  }
 }
 }
</script>

也可以參考連結:https://www.jianshu.com/p/623c8b009a85。說得很清楚。可以看到,父元件通過import的方式匯入子元件,並在components屬性中註冊,然後子元件就可以用標籤的形式嵌進父元件了。

在 <child ref="msg"></child>中,將子元件例項指給了$ref,所以通過this.$refs.msg.getMessage('我是子元件一!')呼叫到子元件的getMessage方法,將引數傳遞給子元件。

prop和$ref之間的區別

(1)prop 著重於資料的傳遞,它並不能呼叫子元件裡的屬性和方法。像建立文章元件時,自定義標題和內容這樣的使用場景,最適合使用prop。

(2)$ref 著重於索引,主要用來呼叫子元件裡的屬性和方法,其實並不擅長資料傳遞。而且ref用在dom元素的時候,能使到選擇器的作用,這個功能比作為索引更常有用到。

使用$emit實現子父通訊

prop和$ref主要都是父元件向子元件通訊,而通過$emit 實現子元件向父元件通訊。

vm.$emit( event, arg )

$emit 繫結一個自定義事件event,當這個這個語句被執行到的時候,就會將引數arg傳遞給父元件,父元件通過@event監聽並接收引數。

<template>
 <div>
 <h1>{{title}}</h1>
 <child @getMessage="showMsg"></child>
//getMessage是子元件的自定義事件,
//父元件通過 @事件名="方法" = > @getMessage = "showMsg",
//將自定義事件getMessage的引數傳給父元件的方法showMsg 
//因此實現了子元件向父元件傳值
 </div>
</template>
 
<script>
 import Child from '../components/child.vue'
 export default {
 components: {Child},
 data(){
  return{
  title:''
  }
 },
 methods:{
  showMsg(title){
  this.title=title;
  }
 }
 }
</script>
<template>
 <h3>我是子元件!</h3>
</template>
<script>
 export default {
 mounted: function () {
  this.$emit('getMessage', '我是父元件!')  //子元件通過$emit 繫結一個自定義事件event,當這個這個語句被執行到的時候,就會將引數arg傳遞給父元件
 }
 }
</script>

三、元件的事件

Vue事件分為普通事件和修飾符事件,這裡我們主要介紹修飾符事件。

Vue 提供了大量的修飾符封裝了這些過濾和判斷,讓開發者少寫程式碼,把時間都投入的業務、邏輯上,只需要通過一個修飾符去呼叫。我們先來思考這樣問題:怎樣給這個自定義元件 custom-component 繫結一個原生的 click 事件?

<custom-component>元件內容</custom-component>

如果你的回答是<custom-component @click="xxx">,那就錯了。這裡的 @click 是自定義事件 click,並不是原生事件 click。繫結原生的 click 是這樣的:

<custom-component @click.native="xxx">元件內容</custom-component>

實際開發過程中離不開事件修飾符,常見事件修飾符有以下這些:

1.表單修飾符

(1)lazy

在預設情況下,v-model在每次input事件觸發後將輸入框的值與資料進行同步 。你可以新增lazy修飾符,從而轉變為使用change事件進行同步。適用於輸入完所有內容後,游標離開才更新檢視的場景。

(2)trim

如果要自動過濾使用者輸入的首尾空白字元,可以給 v-model 新增 trim 修飾符:

<input v-model.trim="msg">

這個修飾符可以過濾掉輸入完密碼不小心多敲了一下空格的場景。需要注意的是,它只能過濾首尾的空格!首尾,中間的是不會過濾的。

(3)number

如果想自動將使用者的輸入值轉為數值型別,可以給 v-model 新增 number 修飾符:

<input v-model.number="value" type="text" />

2.事件修飾符

<!-- 阻止單擊事件繼續傳播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再過載頁面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修飾符可以串聯 -->
<a v-on:click.stop.prevent="doThat"></a>

三、插槽

1.什麼是插槽

插槽就是子元件中的提供給父元件使用的一個佔位符,用<slot></slot> 表示,父元件可以在這個佔位符中填充任何模板程式碼,如 HTML、元件等,填充的內容會替換子元件的<slot></slot>標籤。

如下程式碼:

(1)在子元件中放一個佔位符

(2)在父元件中給這個佔位符填充內容:

(3)展示的效果

如果子元件中沒有放插槽,同樣的父元件中在子元件中填充內容,會是啥樣的:

(1)子元件程式碼無插槽:

(2)父元件照常填充內容:

(3)展示的效果:

所以如果子元件沒有使用插槽,父元件如果需要往子元件中填充模板或者html, 是沒法做到的。

2.插槽的使用

最簡單的使用如上所示,不再贅述。接下來看看,插槽其他使用場景。

(1)具名插槽

具名插槽其實就是給插槽取個名字。一個子元件可以放多個插槽,而且可以放在不同的地方,而父元件填充內容時,可以根據這個名字把內容填充到對應插槽中。

1.子元件的程式碼,設定了兩個插槽(header和footer):

2.父元件填充內容,父元件通過 v-slot:[name] 的方式指定到對應的插槽中

3.展示的效果:

接下來再來看看,父元件中填充內容的時候,順序調換下,看下能不能內容能不能對應上:

1. 子元件程式碼不變,父元件程式碼中填充順序調換下(如圖,在父元件中,footer 和 header 的填充位置對換):

2.展示的效果:

  由此看出,即使父元件對插槽的填充的順序打亂,只要名字對應上了,就可以正確渲染到對應的插槽中。即:父元件填充內容時,是可以根據這個名字把內容填充到對應插槽中的.

(2)預設插槽

預設插槽就是指沒有名字的插槽,子元件未定義的名字的插槽,父級將會把未指定插槽的填充的內容填充到預設插槽中。

示例程式碼如下:
1.子元件程式碼定義了一個預設插槽:

  2.父元件給預設插槽填充內容:

  

  3. 展現的內容

  

注意

1. 父級的填充內容如果指定到子元件的沒有對應名字插槽,那麼該內容不會被填充到預設插槽中。

2. 如果子元件沒有預設插槽,而父級的填充內容指定到預設插槽中,那麼該內容就“不會”填充到子元件的任何一個插槽中。

3. 如果子元件有多個預設插槽,而父元件所有指定到預設插槽的填充內容,將“” “全都”填充到子元件的每個預設插槽中。

(3)作用域插槽

作用域插槽其實就是帶資料的插槽,即帶引數的插槽,簡單的來說就是子元件提供給父元件的引數,該引數僅限於插槽中使用,父元件可根據子元件傳過來的插槽資料來進行不同的方式展現和填充插槽內容。

作用域插槽要求,在slot上面繫結資料:

<slot name="up" :data="data"></slot>
 export default {
    data: function(){
      return {
        data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
      }
    },
}

插槽最後顯示不顯示是看父元件有沒有在child下面寫模板。(子元件中插入佔位符,父元件在引入子元件時寫模板填充佔位符。)

寫了,插槽就總得在瀏覽器上顯示點東西,東西就是html該有的模樣,沒寫,插槽就是空殼子,啥都沒有。

有html模板的情況,就是父元件會往子元件插模板的情況,那到底插一套什麼樣的樣式呢,這由父元件的html+css共同決定,但是這套樣式裡面的內容呢?

正因為作用域插槽綁定了一套資料,父元件可以拿來用。於是,情況就變成了這樣:樣式父元件說了算,但內容可以顯示子元件插槽繫結的

再來對比,作用域插槽跟單個插槽和具名插槽的區別。因為單個插槽和具名插槽不繫結資料,所以父元件提供的模板一般要既包括樣式又包括內容;而作用域插槽,父元件只需要提供一套樣式(在確實用作用域插槽繫結的資料的前提下)。 子元件內,定義slot,繫結屬性,在父元件內使用時,在child內,通過slot-scope接受一個物件引數,可以根據引數,定製每個代辦項。slot-scope="{todo}"可以結構獲取引數。可是定義多個作用域插槽。

下面的例子,父元件提供了三種樣式(分別是flex、ul、直接顯示),都沒有提供資料,資料使用的都是子元件插槽自己繫結的那個陣列(一堆人名的那個陣列)。

父元件:

<template>
  <div class="father">
    <h3>這裡是父元件</h3>
    <!--第一次使用:用flex展示資料-->
    <child>
      <template slot-scope="user"> //通過slot-scope接受了物件引數
        <div class="tmpl">
          <span v-for="item in user.data">{{item}}</span>
        </div>
      </template>

    </child>

    <!--第二次使用:用列表展示資料-->
    <child>
      <template slot-scope="user">
        <ul>
          <li v-for="item in user.data">{{item}}</li>
        </ul>
      </template>

    </child>

    <!--第三次使用:直接顯示資料-->
    <child>
      <template slot-scope="user">
       {{user.data}}
      </template>

    </child>

    <!--第四次使用:不使用其提供的資料, 作用域插槽退變成匿名插槽-->
    <child>
      我就是模板
    </child>
  </div>
</template>

子元件:

<template>
  <div class="child">

    <h3>這裡是子元件</h3>
    // 作用域插槽
    <slot  :data="data"></slot> 
  </div>
</template>

 export default {
    data: function(){
      return {
        data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
      }
    }
}

在vue v2.6.0中,新引入了v-slot指令,他取代了slot和slot-scope這兩個目前已經被廢棄但是為被移除的特性。

// 根元件
<template>
    <div>
        <mo>
            <template v-slot:header="slotProps">
                <h1>{{slotProps.header + ' ' + msg}}</h1>
                
            </template>

            <p>A paragraph for the main content.</p>
            <p>And another one.</p>

            <template v-slot:footer>
                <p>Here's some contact info</p>
            </template>
        </mo>
    </div>
</template>

<script>
    import mo from './module.vue'
    export default {
        components: {
            mo
        },
        data() {
            return {
                msg: '這是根元件的訊息'
            }
        }
    }
</script>

// 子元件
<template>
    <div>
        --header start--
        <header>
            <slot name="header" :header="header"></slot>
        </header>
        --header over--
        <div></div>
        --default start--
        <slot></slot>
        --default over--
        <div></div>
        --footer start--
        <footer>
            <slot name="footer"></slot>
        </footer>
        --dooter over--
    </div>
</template>

<script>
    export default {
        data() {
            return {
                header: '來自子元件的頭部訊息'
            }
        }
    }
</script>

<style scoped>

</style>

a.元件中可以使用template標籤,加v-slot指令制定具名插槽,當沒有指定插槽name時,默認出口會帶有隱含的名字“default”。

b.根元件可以利用v-slot:header="slotProps"接受元件中的訊息,元件中只需要在<slot name="header" :header="header"></slot>就可以了

c.如果被提供的內容只有一個預設插槽時,元件的標籤可以直接被當做插槽的模板來使用<mo v-slot="slotProps">

d.動態引數也可是使用到插槽當中<mo v-slot=[dynamicSlotName]>

e.v-slot的縮寫是#,但是如果使用#的話,必須始終使用具插槽來代替
<mo #default="slotProps">