1. 程式人生 > >從壹開始前後端分離 [ Vue2.0+.NET Core2.1] 二十║Vue基礎終篇:元件詳解+專案說明

從壹開始前後端分離 [ Vue2.0+.NET Core2.1] 二十║Vue基礎終篇:元件詳解+專案說明

緣起

新的一天又開始啦,大家也應該看到我的標題了,是滴,Vue基礎基本就到這裡了,咱們回頭看看這一路,如果你都看了,並且都會寫了,那麼現在你就可以自己寫一個Demo了,如果再瞭解一點路由,ajax請求(這裡是axios),那麼你就可以準備面試前端了,哈哈,當然沒有這麼誇張,往後的路還很長,至少咱們基礎都會了。

這裡咱們再溫習下之前講了哪些基礎知識:

一共是五篇,基本已經涵蓋了Vue的基礎知識,今天呢,再說完元件以後,明天就正式開始搭建本地腳手架,終於開始安裝軟體了[ 哭笑 ],我這幾天考慮了一下,在之後的講解中,可能需要兩個小專案的講解,第一個就是我現在自己用到的一個,大家其實也可以看看 

http://vue.blog.azlinli.com(買的伺服器不好,首次載入慢),也不是啥隱私,這個是我之前練習的時候自己瞎玩的,只有首頁和詳情頁,用的資料就是咱們在之前系列裡講到的.net core api,這個可能在本週,或者本週末說到,主要的就是把之前的講解給穿起來,然後再說下如何使用路由 v-router 和 ajax請求——axios,這樣我這個專案就說到這裡,然後第二個也是一個部落格系統,用的是一套資料,只不過是用到了 Nuxt 框架了,基本結構又發生了變化,專案整體被封裝了,更趨於工程化,至於為什麼要用到這個,就是因為它可以解決 MVVM 前後端分離的 SEO 的老大難的問題,大家可以先問問度娘,到時候都會說到滴。好啦,開始今天的基礎篇最後一章節 —— 深入瞭解下元件。

零、今天要完成天青色的部分

 一、元件的基本使用

1、註冊元件

上篇檔案我們也說到了,註冊元件的其中一個辦法就是 Vue.component()方法,先傳入一個自定義元件的名字,然後傳入這個元件的配置。

 Vue.component('mycomponent',{
    template: `<div>我的元件</div>`,
    data () {
      return {
        message: '老張的哲學'
      }
    }
  })

定義好後,我們就可以在Vue例項所定義的DOM元素內使用它(就是我們在new vue的時候的 el 節點),這裡我們的頁尾元件,全域性元件,可以在其他地方使用

 <div id="app">
    <mycomponent></mycomponent>
    <my-component></my-component>
</div>
<script>

//注意要在vue例項之前去定義,不然渲染頁面的時候,會報錯
  // 定義一個名為 footer-vue 的新元件
 Vue.component('footer-vue', {
     template: `
             <div id="footer-vue">
                 <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p>
                 <p>
                     <a href="#">京ICP備00000000號</a>
                 </p>
             </div>
             `,
     data () {
         return {
             message: 'hello world'
         }
     }
 })

  var app = new Vue({
    el: '#app',//沒錯,vue例項所定義的DOM元素就是這個,超過了這個區域,定義的元件會無效
    data: {
    },
  })
</script>

上邊,我們定義的元件是一個全域性的元件,也就是說如果我們定義了多個 vue例項,我們都可以使用這一個元件,這就是全域性的,

當然,既然有全域性的,我們也有區域性的(我們對聯絡方式定義區域性元件,表示只有在當前頁面的app元素內使用):

注意:全域性的元件是 component,而 區域性的是 components

var app = new Vue({
    el: '#app',
    data: {
    },
    components: {
          'my-component': {//這個就是我們區域性元件的名字 在頁面內使用 <my-component></my-component>
         template: `
             <ul class ="contact-list non-style-list">
     <li>
         <b class ="twitter">TWITTER</b>: <a href="#">@laozhang</a>
     </li>
     <li>
         <b class ="weibo">微博</b>: <a href="#">@laozhang</a>
     </li>
     <li>
         <b class ="zhihu">知乎</b>: <a href="#" ></a>
     </li>
     <li>
         <b class ="github">GITHUB</b>: <a href="https://github.com/anjoy8">anjoy8</a>
     </li>
     <li>
         <b class ="email">EMAIL</b>: <a href="mailto:[email protected]">randypriv at azlinli</a>
     </li>
 </ul>
             `,

      data () {
          return {
              message: 'hello world two'
          }
      },

      directives:{//自定義區域性指令,使用的時候,直接可以 <my-component v-focus><my-component>

        focus;{

          inserted(el){

            el.focus();

          }

        }

      }

      }
    }
  })

2、元件的規範定義——單個根元素 + Data函式化

觀察一下上邊兩種寫法與的特點,大家應該也能說出來:

相同點:元件的模板只能有一個根節點,或者說根標籤只能有一個(第一個的根元素是 <div>,第二個根元素是 <ul>),如果定義成這樣就是不允許的,這裡是兩個根元素 <div> 和 <a>:

template: `<div>我的地盤聽我的,哈哈,只能在當前我的Vue例項中使用</div>
<a>我的一個標籤</a>
      `,

我們看到在定義元件的時候和平時定義的 data 不一樣,這裡的定義一定要是一個函式,因為如果像Vue例項那樣,傳入一個物件,由於JS中物件型別的變數實際上儲存的是物件的引用,所以當存在多個這樣的元件時,會共享資料,導致一個元件中資料的改變會引起其他元件資料的改變。而使用一個返回物件的函式,每次使用元件都會建立一個新的物件,這樣就不會出現共享資料的問題來了。

 Vue.component('footer-vue', {
     template: `
             <div id="footer-vue">
                 <p>
                     <a href="#">京ICP備00000000號</a>
                 </p>
             </div>
             `,
     data :{//這是錯誤栗子
             message: 'hello world'//我們用普通屬性的方法
     }
 })

如果我們按照一個屬性的寫法的話,頁面會成功的報了一個這樣的錯誤,而且大家注意,這個錯誤是出現在哪裡的,沒錯就是掛載結束前,也就是說,和例項化資料沒影響,但是在掛載到頁面,頁面渲染的時候,出現了這個錯誤,所以大家在初學的時候,還是要多瞭解下生命週期的概念。

 注意:因為元件是可複用的 Vue 例項,所以它們與 new Vue 接收相同的選項,例如 datacomputedwatchmethods 以及生命週期鉤子等。僅有的例外是像 el 這樣根例項特有的選項。

3、另外一種註冊方式,通過 全域性API:Vue.extend()建立,然後由component來註冊,兩步

//  extend 建立元件
var MyComponent = Vue.extend({
  template: '<div>A custom component!</div>'
});

// component註冊 元件
Vue.component('my-component', MyComponent);//使用到了 extend 建立的元件
var vm = new Vue({
  el: '#example',
  data: {
       
  }
})

 兩種寫法沒有什麼太多的區別,基本來說

extend 是構造建立一個元件的語法器,你給它引數 他給你建立一個元件, 然後這個元件,你可以作用到Vue.component 這個全域性註冊方法裡, 也可以在任意vue模板裡使用apple元件

var apple = Vue.extend({ 
…. 
}) 
Vue.component(‘apple’,apple)

你可以作用到vue例項或者某個元件中的components屬性中並在內部使用apple元件

new Vue({ 
components:{ 
apple:apple 

})

可見上邊的定義過程比較繁瑣,也可以不用每次都呼叫兩個,可以直接用 Vue.component 建立 ,也可以取元件 例如下

var apple = Vue.component(‘apple’)

4、模板html 可以單寫出來

<template id="temApp">//這裡是定義一個id,
  <div>
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">Form</router-link> |
      <router-link to="/Vuex">Vuex</router-link>
    </div>
    <router-view/>
  </div>
</template>

//在使用的時候,直接引用 #temApp
 Vue.component('footer-vue', {
     template:'#temApp',
     data () {
         return {
             message: 'hello world'
         }
     }
 })

5、動態元件 

你一定在開發中會遇到這樣的需求,就是一個banner的切換:

我們這時候可以使用動態元件,很容易實現這個需求,通過 Vue 的 <component> 元素加一個特殊的 is 特性來實現:

<!-- 元件會在 `currentTabComponent` 改變時改變 -->
<component v-bind:is="currentTabComponent"></component>

在上述示例中,currentTabComponent 可以包括

  • 已註冊元件的名字,或
  • 一個元件的選項物件
<div id="dynamic-component-demo" class="demo">
  <button
    v-for="tab in tabs"//for 迴圈展示 banner
    v-bind:key="tab"
    v-bind:class="['tab-button', { active: currentTab === tab }]"//繫結樣式,當前元件增加 active 樣式
    v-on:click="currentTab = tab"
  >{{ tab }}</button>
<!-- 元件的使用 通過currentTabComponent 來動態展示是哪一個元件 -->
  <component
    v-bind:is="currentTabComponent"//通過 is 特性,來動態實現元件,核心
    class="tab"
  ></component>
</div>
//定義三個元件,可以比如是我們的三個頁面,
Vue.component('tab-home', { 
    template: '<div>Home component</div>' //元件1,也就是頁面1
})
Vue.component('tab-posts', { 
    template: '<div>Posts component</div>' //元件2,頁面2
})
Vue.component('tab-archive', { 
    template: '<div>Archive component</div>' //元件3,頁面3
})

new Vue({
  el: '#dynamic-component-demo',
  data: {
    currentTab: 'Home',//當前banner名稱
    tabs: ['Home', 'Posts', 'Archive']//定義三個banner
  },
  computed: {//計算屬性,實時監控獲取當然banner的值,並返回到頁面
    currentTabComponent: function () {
      return 'tab-' + this.currentTab.toLowerCase()//元件名稱拼串,一定要和上邊的三個元件名對應
    }
  }
})

 注意:這裡可以使用 <keep-alive> 來快取當然元件的內容

 <keep-alive> 

  <component :is="currentTabComponent"></component>

 <keep-alive> 

 二、屬性Props —— 父子通訊(父傳子)

在 Vue 中,父子元件的關係可以總結為 prop 向下傳遞,事件向上傳遞。父元件通過 prop 給子元件下發資料,子元件通過事件給父元件傳送訊息,這裡咱們先說下向下傳遞,通過屬性Props屬性來完成。

1、使用動態屬性Props,可以將父元件的資料傳遞給子元件,從而達到父子通訊的第一步,舉個栗子

還記得之前咱們說的,Vue 其實就是由許許多多個元件拼接而成,高效複用,相互之間可以傳值,但是又不受影響,最常見的應用就是:元件 A 在它的模板中使用了元件 B。它們之間必然需要相互通訊:父元件可能要給子元件下發資料,子元件則可能要將它內部發生的事情告知父元件。大家第一次使用的時候可能會有點兒不舒服,但是使用熟練以後,就會發現真的得心應手,所以咱們就先看看元件是如何通訊的。

首先大家還記得咱們定義的頁尾元件麼,就是剛剛說到的。咱們看到最下邊是備案號,現在想在備案號旁邊加上咱的暱稱”老張的哲學“,想想很簡單嘛,想想肯定不能直接寫死資料吧,正好看到頁面內定義vue例項的時候,有這個屬性嘛,直接放到咱們的頁尾元件裡,嗯就是這樣:

      // 定義一個名為 footer-vue 的新元件
  Vue.component('footer-vue', {
      template: `
                  <div id="footer-vue">
                      <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p>
                      <p>
                          <a href="#">京ICP備00000000號{{authorHtml}}</a>
                      </p>
                  </div>
                  `,
      data () {
          return {
              message: 'hello world'
          }
      }
  })

然後滿懷開心的重新整理頁面一看,額報錯了:

然後很熟練的必應翻譯了一下(這是一個反面教材,大家要好好學習英文,多看國外教程 [苦笑] ),得到這樣的:屬性或方法  "authorHtml " 不應該是在例項上定義的, 而是在呈現過程中引用的。通過初始化屬性, 確保此屬性在資料選項或基於類的元件中是被動的。說人話就是,這裡不能使用例項上定義的值,好吧,查閱資料發現,元件只能被動的接受父元件的傳引數,嗯於是乎我們這樣寫:

 <footer-vue :foo="author"></footer-vue>//在自定義的元件上,新增一個動態屬性,然後屬性的值 author 是父元件的,這個時候父元件就是已經把值給發過去了

這個時候,我們就需要在子元件裡接受一下

 // 定義一個名為 footer-vue 的新元件
 Vue.component('footer-vue', {
     template: `
             <div id="footer-vue">
                 <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p>
                 <p>
                     <a href="#">京ICP備00000000號{{foo}}</a>//這裡根據自身的props的引數來賦值
                 </p>
             </div>
             `,
     props: ['foo'],//這裡根據元件的props屬性,來被動接受元件傳遞來的引數
     data () {
         return {
             message: 'hello world'
         }
     }
 })

重新整理頁面,這時候就真正的成功了。

2、使用靜態Props傳值

這裡我們得到的結果和上邊的是一樣的,直接是一個字串結果,而不是一個變數屬性。

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

<child message="老張的哲學"></child>

3、注意大小寫的命名的寫法

注意:HTML 特性是不區分大小寫的。所以,當使用的不是字串模板時,camelCase (駝峰式命名) 的 prop 需要轉換為相對應的 kebab-case (短橫線分隔式命名),比如 如何上邊的foo 寫成了 fooPro ,那我們定義屬性的時候,就應該寫 foo-pro

<footer-vue :foo-pro="author"></footer-vue>

 如果我們寫了 fooPro 這樣的寫法,掛載頁面的時候,就會警告,並且不會成功渲染

如果你使用字串模板,則沒有這些限制。(字串模板:指的是在元件選項裡用 template:"" 指定的模板,換句話說,寫在 js 中的 template:"" 中的就是字串模板。)

 三、自定義事件 —— 子傳父

我們知道,父元件使用 prop 傳遞資料給子元件。但子元件怎麼跟父元件通訊呢?這個時候 Vue 的自定義事件系統就派得上用場了。

1、使用 v-on 繫結自定義事件

每個 Vue 例項都實現了事件介面,即:

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

Vue 的事件系統與瀏覽器的 EventTarget API 有所不同。儘管它們的執行起來類似,但是 $on 和 $emit 並不是addEventListener 和 dispatchEvent 的別名。

另外,父元件可以在使用子元件的地方直接用 v-on 來監聽子元件觸發的事件。

//1、子元件內,有一個click,當點選的時候 觸發 incrementCounter 方法
//2、方法被觸發以後,向父元件 傳送一個訊號廣播,並傳遞引數 counter,名字就是 increment。
//3、父元件通過監聽,來獲取到這個廣播訊號 increment ,然後觸發 incrementTotal 方法
//4、incrementTotal 被觸發,獲取到引數 counter 值,執行相應的操作
<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>//3、父元件通過監聽,來獲取到這個廣播訊號 increment ,然後觸發 incrementTotal 方法
</div>


Vue.component('button-counter', {
  template: '<button v-on:click="incrementCounter">{{ counter }}</button>',//1、子元件內,有一個click,當點選的時候 觸發 incrementCounter 方法
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment',this.counter)//2、方法被觸發以後,向父元件 傳送一個訊號廣播,並傳遞引數 counter,名字就是 increment。
    }
  },
mounted(){//常用,掛載完成後執行
    this.incrementCounter();
  } })
new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal(counter) {//4、incrementTotal 被觸發,獲取到引數 counter 值,執行相應的操作 this.total = counter } } })

 上邊註釋的已經很清楚,就好像冒泡一樣的,往上走,和 父傳子,正好相反。

2、使用自定義事件的表單輸入元件

自定義事件可以用來建立自定義的表單輸入元件,使用 v-model 來進行資料雙向繫結。要牢記:

<input v-model="something">

這不過是以下示例的語法糖:

<input
  v-bind:value="something"
  v-on:input="something = $event.target.value">

所以在元件中使用時,它相當於下面的簡寫:

<custom-input
  v-bind:value="something"
  v-on:input="something = arguments[0]">
</custom-input>
<div id="app">
      <custom-input v-model="something"></custom-input>
    <br/>
    {{something}}
</div>

// 註冊
Vue.component('custom-input', {
  props:['something'],
  template: '<input type="text"  v-bind:value="something" v-on:input="updateValue($event.target.value)"/>',
  methods:{
      updateValue:function(value){
           this.$emit('input', value)
      }
  }
})
var vm = new Vue({
  el: '#app',
  data: {
       something:''
  }
  
})

四、使用插槽slot分發內容(完善中)

2018-09-14 更新:

如果我們定義了一個元件 O,需要在 A、B、C三個頁面使用,在O中有一部分是三個子元件相同的,有一部分是各自的不同的,這個時候我們就可以使用 slot 分發;

內容分發的作用,就是為了元件的靈活性,在一個模板中,可以供呼叫者自定義部分

//宣告模板
<template id="mysolt">
  <div>
    <h3>我的公共的相同的內容</h3>
    <slot name="s1"></slot>
  </div>
</template>

//定義元件
 Vue.component('my-solt', {
     template:'#mysolt',
     data () {
         return {
             message: 'hello world'
         }
     }
 })


//在a頁面
<body>
<div id="app">
  <my-solt>
    <ul slot="s1">
      <li>a</li>
      <li>a</li>
      <li>a</li>
    </ul>
  </my-solt>
  
</div>

</body>

//在b頁面
<body>
<div id="app">
  <my-solt>
    <ul slot="s1">
     <p>b</p>
     <p>b</p>
     <p>b</p>
    </ul>
  </my-solt>
  
</div>

</body>

//在c頁面
<body>
<div id="app">
  <my-solt>
    <ul slot="s1">
<div>ccc</div>
    </ul>
  </my-solt>
  
</div>

</body>

在使用元件時,我們常常要像這樣組合它們:

<app>
  <app-header></app-header>
  <app-footer></app-footer>
</app>

注意兩點:

  • <app> 元件不知道它會收到什麼內容。這是由使用 <app> 的父元件決定的。
  • <app> 元件很可能有它自己的模板。

為了讓元件可以組合,我們需要一種方式來混合父元件的內容與子元件自己的模板。使用特殊的 <slot> 元素作為原始內容的插槽。

一個常見錯誤是試圖在父元件模板內將一個指令繫結到子元件的屬性/方法:

<!-- 無效 -->
<child-component v-show="someChildProperty"></child-component>

正確做法:

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

1、單個插槽

假定 my-component 元件有如下模板:

<div>
  <h2>我是子元件的標題</h2>
  <slot>
    只有在沒有要分發的內容時才會顯示。
  </slot>
</div>

父元件模板:

<div>
  <h1>我是父元件的標題</h1>
  <my-component>
    <p>這是一些初始內容</p>
    <p>這是更多的初始內容</p>
  </my-component>
</div>

渲染結果:

 
<div>
  <h1>我是父元件的標題</h1>
  <div>
    <h2>我是子元件的標題</h2>
    <p>這是一些初始內容</p>
    <p>這是更多的初始內容</p>
  </div>
</div>

五、結語

今天簡單的說了下關於元件的一些問題,因為事件的問題,還沒有說完,還在進一步整理當中,大家可以以後進一步的瀏覽本博文,通過元件的學習,大家在Vue開發的道路上又進了一步,好啦,關於Vue的基礎知識就是這麼多了,明天開始進入程式碼練習啦~~

六、CODE