Vue筆記系列(二)Vue.js漸進

Vue筆記系列
一、Vue.js入門
三、Vue.js進階

API

如下會隨用隨記一些API,可能會不按期更新。html

  • Vue.component( id, [definition] ) .

註冊或獲取全局組件。註冊還會自動使用給定的id設置組件的名稱。vue

// 註冊組件,傳入一個擴展過的構造器
Vue.component('my-component', Vue.extend({ /* ... */ }))
// 註冊組件,傳入一個選項對象(自動調用 Vue.extend)
Vue.component('my-component', { /* ... */ })
// 獲取註冊的組件(始終返回構造器)
var MyComponent = Vue.component('my-component')
  • Vue.extend(options) .

使用基礎 Vue 構造器,建立一個「子類」。參數是一個包含組件選項的對象。注意:data 選項是特例,在 Vue.extend() 中它必須是函數。git

<!-- HTML -->
<div id="mount-point"></div>
// 建立構造器
var Profile = Vue.extend({
      template: '<p v-text="name"></p>',
          data: function () {
            return {
                  name: '第一個構造器!'
            }
      }
})
// 建立 Profile 實例,並掛載到一個元素上(會替換#mount-pointer)。掛載的組件會把被掛載的元素替換掉。
new Profile().$mount('#mount-pointer');

結果以下:github

<p>第一個構造器!</p>

若是掛載元素不想被替換掉,能夠用如下方法:segmentfault

var component = new Profile().$mount()
document.getElementById('mount-pointer').appendChild(component.$el)
  • Vue.set( object, key, value ) 設置對象的屬性。

Vue 不能檢測到對象屬性的添加或刪除。因爲 Vue 會在初始化實例時對屬性執行 getter/setter 轉化過程,因此屬性必須在 data 對象上存在才能讓 Vue 轉換它,這樣才能讓它是響應的。Vue 不容許在已經建立的實例上動態添加新的根級響應式屬性(因此,set方法的object參數也不能是 Vue 實例,或者 Vue 實例的根數據對象)。可使用 Vue.set(object, key, value) 方法將響應屬性添加到嵌套的對象
以前說過的v-for指令,當你利用索引直接設置一個項時,例如上文的example1.words[0] = {text: 'A'},若是想讓視圖更新,其中一種方法就是用set。api

Vue.set(example1.items, 0, {text: 'A'})
  • Vue.nextTick( [callback, context] )  涉及到Vue的異步更新隊列

在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。數組

<div id="example">{{message}}</div>
 
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改數據
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

爲毛第一次DOM裏面的內容沒有變,拿不到改變的內容,通過nextTick方法後才才能拿到改變的內容。
這是由於,當你設置 vm.someData = 'new value' ,該組件不會當即從新渲染。當刷新隊列時,組件會在事件循環隊列清空時的下一個「tick」更新。瀏覽器


全局配置

全局配置——Vue.config 是一個對象,包含 Vue 的全局配置。有如下屬性:服務器

  • silent    Vue.config.silent = true; 取消 Vue 全部的日誌與警告,false時開啓。
  • devtools  Vue.config.devtools = true; 配置是否容許 vue-devtools 檢查代碼。開發版本默認爲 true,生產版本默認爲 false。vue-devtools指的是一個瀏覽器插件,在谷歌應用裏面有。

vue.devtools.png

安裝以後,是在google 開發者工具的這裏找到。
2.pngapp


組件

1、使用組件

  • 註冊

一、全局註冊
註冊一個全局組件,可使用 Vue.component(tagName, options)
注意:對於自定義標籤名,Vue.js 不強制要求遵循 W3C規則 (小寫,而且包含一個短槓),可是建議這樣寫。
組件在註冊以後,即可以在父實例的模塊中以自定義元素的形式使用。謹記要確保在初始化根實例以前註冊了組件。而且, el 和 data 選項必須是函數

<!-- HTML  -->
<div id="example">
  <my-component></my-component>
</div>
// 註冊,這就是所謂的語法糖,由於下面的方法有點麻煩。
Vue.component('my-component', {
      template: '<div>個人第一個組件!</div>'
})
// 建立父實例
new Vue({
      el: '#example'
})

渲染爲:

<div id="example">
      <div>個人第一個組件!</div>
</div>

二、構造器用做組件
可使用 Vue.extend({...}) 建立一個組件構造器,extend 方法建立基礎Vue構造器的子類,參數是一個對象,包含組件選項,這裏要注意的特例是 el 和 data 選項,在 Vue.extend() 中,它們必須是函數註冊組件的component方法也同樣。這是由於,若是使用一個數據對象(是一個引用),那麼全部的組件實例都共享這一個對象,這樣就會牽一髮而動全身。
有了這個構造器,咱們既能夠用全局註冊的方式用 Vue.component(tag, constructor) 註冊,也能夠利用該構造器構建一個實例,而後用 Vue.$mount() 將該組件實例添加到DOM樹上。

<!-- HTML  -->
<div id="example">
  <my-component></my-component>
</div>
// 建立構造器
var Profile = Vue.extend({
      template: '<p v-text="name"></p>',
          data: function () {
            return {
                  name: '第一個構造器組件!'
            }
      }
})
// 註冊
Vue.component('my-component',Profile)
// 建立父實例
new Vue({
      el: '#example'
})

渲染爲:

<div id="example">
      <p>第一個構造器組件!</p>
</div>

三、局部註冊
經過使用組件實例選項註冊,可使組件僅在另外一個實例/組件的做用域中可用。即在註冊的對象參數中添加 components 成員,components成員的標籤就只在該組件內使用,不在全局DOM樹中使用局部註冊的組件。

//實例做用域
var Child = {
  template: '<div>一個局部組件!</div>'
}
new Vue({
  // ...
  components: {
    // <my-component> 將只在父模板可用
    'my-component': Child
  }
})
 
//組件做用域
<div id="comp-ex">
      <contact></contact>
</div>
<script>
  var Person = Vue.extend({   // constructor
    template: "<div><span>name:</span> {{name}}  <span>Age: </span> {{age}}</div>",
    data: function() {
      return {
        name: ' li ming',
        age: 22
      }
    }
  });
  var Contact = Vue.extend({
    template: "<div><span>Tel: </span> {{tel}}, <span>E-mail: </span> {{email}}<person></person></div>",
    data: function() {
      return {
        tel: '152-0101-1010',
        email: 'admin#163.com'
      }
    },
    components: {
      'person': Person        // 局部註冊 person 標籤
    }
  })
  Vue.component('contact', Contact)
  var you = new Vue({        // init component, render template
    el: '#comp-ex'
  })
</script>

子組件只能在父組件的template中使用。注意下面兩種子組件的使用方式是錯誤的:

  1. 以子標籤的形式在父組件中使用
<div id="app">
    <parent-component>
        <child-component></child-component>
    </parent-component>
</div>

由於當子組件註冊到父組件時,Vue.js會編譯好父組件的模板,模板的內容已經決定了父組件將要渲染的HTML。
<parent-component>…</parent-component>至關於運行時,它的一些子標籤只會被看成普通的HTML來執行,<child-component></child-component>不是標準的HTML標籤,會被瀏覽器直接忽視掉。

  1. 在父組件標籤外使用子組件
<div id="app">
    <parent-component>
    </parent-component>
    <child-component>
    </child-component>
</div>

運行這段代碼,瀏覽器會提示如下錯誤:

在父組件標籤外使用子組件報錯

四、is特性
一些 HTML 元素,如<ul> <ol> <table> <select>,限制什麼元素能夠放在它裏面。自定義元素不在白名單上,將被放在元素的外面,於是渲染不正確。這時應當使用 is 特性,指示它是一個自定義元素。

<table>
  <my-row>...</my-row>
</table>
<!-- 自定義組件 <my-row> 被認爲是無效的內容,所以在渲染的時候會致使錯誤。須要使用特殊的 is 屬性 -->
<table>
  <tr is="my-row"></tr>
</table>

2、組件通訊

良好的流程: Vue.js 中,父子組件的關係能夠總結爲 props down, events up 。父組件經過 props 向下傳遞數據給子組件,子組件經過 events 給父組件發送消息。
父子組件通訊圖示

一、Prop顯式聲明

  • (1)、使用prop傳遞數據

組件實例的做用域是孤立的。這意味着不能而且不該該在子組件的模板內直接引用父組件的數據。可使用 props 把數據傳給子組件。prop 是父組件用來傳遞數據的一個自定義屬性。子組件須要顯式地用 props選項聲明 「prop」:

讀到這裏,咱們掌握的組件的構造選項對象的屬性包括了:

  • template,要渲染的內容
  • data,數據,必須是一個函數,函數返回一個對象
  • props,從父組件傳遞數據到子組件。
<div id="comp-ex">
<!--  注意在HTML中屬性要寫kebab-case(短橫線隔開) -->
  <child my-message='qiqihaobenben'></child>
  <child my-message='qiqihaobenben'></child>
</div>
<script>
  Vue.component('child', {
      // 就像 data 同樣,prop 能夠用在模板內
      template: '<span>{{message}}</span>',
      //聲明 props
      // HTML特性不區分大小寫,名字形式爲 camelCase 的prop用做特性時,寫在HTML中要用短橫線隔開,不然不起做用,如上。
      props: ['myMessage'],  
      // 一樣也能夠在 vm 實例中像 「this.message」 這樣使用
      data: function (){
          return {
              message: this.myMessage
          }
      }
  })
  var me = new Vue({
    el: '#comp-ex'
  })
<script>

輸出結果爲:

<div id="comp-ex">
     <span>qiqihaobenben qiqihaobenben</span>
</div>
  • (2)、命名風格

HTML特性不區分大小寫,可是名字形式爲 camelCase 的prop用做特性時,須要轉爲 kebab-case(短橫線隔開)。在html中的屬性使用短橫線隔開,而在js的template中的標識使用的駝峯命名,能夠參考上面的例子。

上面的例子使用節點屬性方式向子組件傳遞數據,若是父組件的數據變化,子組件並不會隨之變化,這就是其動態性,若是要綁定動態的值,應該使用 v-bind 指令,這樣每當父組件的數據變化時,也會傳遞給子組件

<div>
  <input v-model="parentMsg">
  <br>
  <child v-bind:my-message="parentMsg"></child>
</div>

動態和非動態這兩種傳遞方式在傳遞數字時是不一樣的,以下:

<!-- 傳遞了一個字符串"1" -->
<comp some-prop="1"></comp>
<!-- 傳遞實際的數字1 -->
<comp v-bind:some-prop="1"></comp>
雖然html渲染的時候並不太區分字符串和數字,可是注意有這種區別。
  • (4)、單項數據流

prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,可是不會反過來。這是爲了防止子組件無心修改了父組件的狀態——這會讓應用的數據流難以理解。
注意:在 JavaScript 中對象和數組是引用類型,指向同一個內存空間,若是 prop 是一個對象或數組,在子組件內部改變它會影響父組件的狀態。

  • (5)、prop驗證

原本不想說的,但是看文檔時怎麼用怎麼不對,後來想通了,因此就拿出來講一下。
看官方文檔能夠知道,type 能夠是下面原生構造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array

其中的除了String,其餘的類型都須要動態綁定才能驗證正確,不然獲得的就是一水的String類型。

//js屬性驗證
props: {
    myAge: {
            type: [Number,Boolean,Object,Array,Function]
            }
      }
<!--如下都會報錯,Expected XXX, got String.-->
<my-component my-age="12"></my-component>
<my-component my-age="true"></my-component>
<my-component my-age="{}"></my-component>
<my-component my-age="[]"></my-component>
<my-component my-age="consoleOne"></my-component>
<!--正確的作法是用v-bind來綁定屬性-->
<my-component v-bind:my-age="12"></my-component>
...
...

另外,default和required這兩個驗證規則,須要組件的屬性徹底不存在時纔會生效

//設置默認值
props: {
        myName: {
            default: 2
        }
    }
//設置必傳項
props: {
        myName: {
            required: true
        }
    }
<!--如下,不論是必傳項或者默認值都不會有效果-->
<my-component my-name="" ></my-component>
<!--只有這個屬性在組件上真的沒有,纔會觸發驗證效果-->
<my-component ></my-component>

以上,當 prop 驗證失敗了,若是使用的是開發版本會拋出一條警告。

二、自定義事件

  • 首先:子組件能夠用 this.$parent 訪問它的父組件,根實例的後代能夠用 this.$root 訪問它,父組件有一個數組 this.$children 包含它全部的子元素。

儘管能夠訪問父鏈上任意的實例,不過子組件應當避免直接依賴父組件的數據。另外子組件中修改父組件的狀態是很是糟糕的,由於:

  • 這讓父組件與子組件緊密地耦合
  • 這樣的話,只看父組件很難理解父組件的狀態,由於它可能被任意子組件修改!理想狀況下,只有組件本身能修改它的狀態

父組件是使用 props 傳遞數據給子組件,若是子組件要把數據傳遞回去,那就是自定義事件!

  • (1)使用v-on綁定自定義事件

每一個 Vue 實例都實現了事件接口(Events interface),即:
父組件——使用 $on(eventName) 監聽事件
子組件——使用 $emit(eventName) 觸發事件

下面的列子跟官網的幾乎同樣,可是區別在於,是經過傳參來改變父組件的狀態的賦值。
<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>
var allTotal = {number: 0}  //這個是統計兩個按鈕的點擊的次數,這樣就能直接賦給父組件
        Vue.component('button-counter', {
          template: '<button v-on:click="increment">{{ counter }}</button>',
          data: function () {
            return {
              counter: 0,
              allCounter: allTotal
            }
          },
          methods: {
            increment: function () {
              this.counter += 1;
              this.allCounter.number += 1;  // 準備給父組件的自定義事件方法傳參
              this.$emit('increment',this.allCounter.number);
            }
          },
        })
        new Vue({
          el: '#counter-event-example',
          data: {
            total: 0
          },
          methods: {
            incrementTotal: function (value) {
              this.total = value;
            }
          }
        })
  • (2)給組件綁定原生事件

在某個組件的根元素上監聽一個原生事件。可使用 .native 修飾 v-on 。這就是說全部的原生事件若是在組件的根元素上不加.native,vue會自動認爲它是自定義事件

  • (3)使用自定義事件的表單輸入組件

使用 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>
<!--改寫成語法糖以下-->
<custom-input v-model="something"></custom-input>

尤爲注意,這個地方有點繞:若是是自定義的表單組件,而且父組件在加載這個表單組件時使用了v-model指令,那麼做爲子組件,接收到的prop應該是value而不是something。


  • (4)子組件的索引
<div id="parent">
  <user-profile ref:profile></user-profile>
</div>
<script>
  var parent = new Vue({el: '#parent'});
  var child = parent.$refs.profile;    // 能夠獲得子組件
</script>
三、內容分發
  • (1)編譯做用域

父組件模板的內容在父組件做用域內編譯;子組件模板的內容在子組件做用域內編譯。說白了,就是一眼看上去,在誰裏面就是誰的

<div id="app">
        <my-component v-show="display"></my-component>
    </div>

    <template id="myComponent">
        <table>
            <tr>
                <th colspan="3">{{msg}}</td>
            </tr>
        </table>
    </template>
    <script>
        var my = Vue.extend({
            template: '#myComponent',
            data : function (){
                return {
                    msg : '這是子組件',
                    display: false
                }
            }
        })
        var vm = new Vue({
            el: '#app',
            data: {
                display: true
            },
            components: {
                'myComponent': my
            }
        })
    </script>

在my-component標籤上使用指令v-show="display",這個display數據是來源於Vue實例vm ,仍是my-component組件呢?
答案是Vue實例

Vue實例vm設置的display是true,因此展現出來

  • (2)單個Slot

下面的代碼在定義my-component組件的模板時,指定了一個<slot></slot>元素。

<div id="app">
    <my-component>
        <h1>這是父組件的內容!</h1>
    </my-component>

    <my-component>
    </my-component>
</div>
<template id="myComponent">
    <div class="content">
        <h2>這是一個子組件!</h2>
        <slot>若是沒有分發內容,則顯示slot中的內容</slot>
        <p>Hello,Vue.js</p>
    </div>
</template>
  
<script>
    Vue.component('my-component', {
        template: '#myComponent'
    })

    new Vue({
        el: '#app'
    })
</script>

第一個<my-component>標籤有一段分發內容 <h1>這是父組件的內容!</h1>,渲染組件時顯示了這段內容。
第二個<my-component>標籤則沒有,渲染組件時則顯示了slot標籤中的內容。

單個slot

  • (3)具名slot

<slot> 元素能夠用一個特殊的屬性 name 來配置如何分發內容。多個 slot 能夠有不一樣的名字。具名 slot 將匹配內容片斷中有對應 slot 特性的元素。

<div id="app">
    <my-component>
        <div slot="header">
            這是一個頭部
        </div>
        <p>neirong</p>
        <p>neirong</p>
        <div slot="footer">
            這是一個底部
        </div>
    </my-component>
</div>
<template id="myComponent">
    <div class="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
</template>
 
<script>
    Vue.component('my-component', {
        template: '#myComponent'
    })

    new Vue({
        el: '#app'
    })
</script>

能夠看出仍然能夠有一個匿名 slot ,它是默認 slot ,做爲找不到匹配的內容片斷的備用插槽。若是沒有默認的 slot ,這些找不到匹配的內容片斷將被拋棄。

  • (4)做用域插槽

做用域插槽是一種特殊類型的插槽,用做使用一個(可以傳遞數據到)可重用模板替換已渲染元素。
數據傳遞,可重用,天然而然的想到循環

  • 基於官網給出一個完整的例子
<div id="app">
    <my-awesome-list :items="items">
      <template slot="item" scope="props">
        <li>{{ props.text }}</li>
      </template>
    </my-awesome-list>
</div>
 
<script>
    Vue.component('my-awesome-list',{
        template: `<ul>
                      <slot name="item"
                        v-for="item in items"
                        :text="item.text">
                   
                      </slot>
                </ul>`,
        props: ['items']
    })
    var demo = new Vue({
        el: '#app',
        data: {
            items: [
                {text: 'aaaaa'},
                {text: 'bbbbb'},
                {text: 'ccccc'},
                {text: 'ddddd'},
                {text: 'eeeee'}
            ]
        }
    })
</script>
3、組件小貼士
  • (1)組件的命名

當註冊組件(或者 props)時,可使用 kebab-case ,camelCase ,或 TitleCase。可是,在 HTML 模版中,請使用 kebab-case 形式:

// 在組件定義中
components: {
  // 使用 kebab-case 形式註冊
  'kebab-cased-component': { /* ... */ },
  // register using camelCase
  'camelCasedComponent': { /* ... */ },
  // register using TitleCase
  'TitleCasedComponent': { /* ... */ }
}
 
<!-- 在HTML模版中始終使用 kebab-case -->
<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<title-cased-component></title-cased-component>

注意:當使用字符串模式時,能夠不受 HTML 的 case-insensitive 限制。


生命週期

生命週期圖示

一、生命週期的各階段
  • (1)beforeCreate

在實例初始化以後,數據觀測(data observer) 和 event/watcher 事件配置以前被調用。

  • (2)created

實例已經建立完成以後被調用。在這一步,實例已完成如下的配置:數據觀測(data observer),屬性和方法的運算, watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前不可見。

  • (3)beforeMount

在掛載開始以前被調用:相關的 render 函數首次被調用。該鉤子在服務器端渲染期間不被調用。

  • (3)mounted

el 被新建立的 vm.$el 替換,並掛載到實例上去以後調用該鉤子。若是 root 實例掛載了一個文檔內元素,當 mounted 被調用時 vm.$el 也在文檔內。該鉤子在服務器端渲染期間不被調用。

  • (4)beforeUpdate

數據更新時調用,發生在虛擬 DOM 從新渲染和打補丁以前。
你能夠在這個鉤子中進一步地更改狀態,這不會觸發附加的重渲染過程。
該鉤子在服務器端渲染期間不被調用。

  • (5)updated

因爲數據更改致使的虛擬 DOM 從新渲染和打補丁,在這以後會調用該鉤子。
當這個鉤子被調用時,組件 DOM 已經更新,因此你如今能夠執行依賴於 DOM 的操做。然而在大多數狀況下,你應該避免在此期間更改狀態,由於這可能會致使更新無限循環。
該鉤子在服務器端渲染期間不被調用。

  • (6)activated

keep-alive 組件激活時調用。該鉤子在服務器端渲染期間不被調用。

  • (7)deactivated

keep-alive 組件停用時調用。該鉤子在服務器端渲染期間不被調用。

  • (8)beforeDestroy

實例銷燬以前調用。在這一步,實例仍然徹底可用。該鉤子在服務器端渲染期間不被調用。

  • (8)destroyed

Vue 實例銷燬後調用。調用後,Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬。該鉤子在服務器端渲染期間不被調用。

二、實例方法
  • (1) vm.$mount( [elementOrSelector] )

手動地掛載一個未掛載的實例,返回值是實例自身。於是能夠鏈式調用其它實例方法。
若是沒有提供 elementOrSelector 參數,模板將被渲染爲文檔以外的的元素,而且你必須使用原生DOM API把它插入文檔中。

var MyComponent = Vue.extend({
  template: '<div>Hello!</div>'
})
// 建立並掛載到 #app (會替換 #app)
new MyComponent().$mount('#app')
// 同上
new MyComponent({ el: '#app' })
// 或者,在文檔以外渲染而且隨後掛載,這種方式不會替換#app
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
  • (2) vm-destroy()

徹底銷燬一個實例。清理它與其它實例的鏈接,解綁它的所有指令及事件監聽器。觸發 beforeDestroy 和 destroyed 的鉤子。
注意:在大多數場景中你不該該調用這個方法。最好使用 v-if 和 v-for 指令以數據驅動的方式控制子組件的生命週期。

  • (3)vm.$nextTick( [callback] )  涉及到Vue的異步更新隊列

回調延遲到下次 DOM 更新循環以後執行。在修改數據以後當即使用它,而後等待 DOM 更新。它跟全局方法 Vue.nextTick 同樣,不一樣的是回調的 this 自動綁定到調用它的實例上。
應用上,在組件內使用 vm.$nextTick() 實例方法特別方便,由於它不須要全局 Vue ,而且回調函數中的 this 將自動綁定到當前的 Vue 實例上:

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: 'not updated'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = 'updated'
      console.log(this.$el.textContent) // => '沒有更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '更新完成'
      })
    }
  }
})
相關文章
相關標籤/搜索