VUE基礎知識詳解

vue.js

參照官網,對各個示例給出自己的理解,較爲詳細的說明vue的基礎知識,自己作爲vue基礎技術字典使用。

vue的引入方式
1.在script標籤中引入
2.在腳手架項目中使用npm install vue來引入
創建一個vue實例

var vm = new Vue({
  // 選項
})

一個vue實例中 數據對象是最關鍵的基礎

var data = { a : 1}  // 數據對象的格式
// 數據對象放在實例中  通常是直接把數據對象直接放到實例的data中 不借助 data這個中間件
var vm = new Vue({
    data: data
})
// 獲得這個實例的property
// 返回數據源中對應的字段
vm.a == data.a // 這裏返回true
// 設置property 也會影響到原數據 應該是同一個內存地址
vm.a = 2
data.a // 這裏輸出的也是2
// 在哪裏修改 都會直接改變值
data.a = 3 
vm.a //這裏的值是3

當這些數據改變時,視圖會進行重渲染。值得注意的是只有當實例被創建時就已經存在於 data 中的 property 纔是響應式的。也就是說如果你添加一個新的 property,所以 在使用過程中 會遇到一個問題 data.b 不在data中 data.b = 3 這樣賦值了 視圖是不會更新的 需要使用$set來賦值 視圖就會重新渲染

通常我們都會直接把需要使用的都放在data裏面 如果最開始沒值 就賦一個空值

data: {
  newTodoText: '',
  visitCount: 0,
  hideCompletedTodos: false,
  todos: [],
  error: null
}

這裏唯一的例外是使用 Object.freeze(),這會阻止修改現有的 property,也意味着響應系統無法再追蹤變化。

var obj = {
  foo: 'bar'
}

Object.freeze(obj)

new Vue({
  el: '#app',
  data: obj
})
<div id="app">
  <p>{{ foo }}</p>
  <!-- 這裏的 `foo` 不會更新! -->
  <button v-on:click="foo = 'baz'">Change it</button>
</div>

除了數據 property,Vue 實例還暴露了一些有用的實例 property 與方法。它們都有前綴 $,以便與用戶定義的 property 區分開來。例如:

var data = { a: 1 }
var vm = new Vue({
  el: '#example',
  data: data
})

vm.$data === data // => true  讀取實例屬性對象 vm的data和定義的data是同一個對象
vm.$el === document.getElementById('example') // => true  獲取Vue實例關聯的DOM元素;

// $watch 是一個實例方法
vm.$watch('a', function (newValue, oldValue) {
  // 這個回調將在 `vm.a` 改變後調用
})
// 還有一個最常用的 $refs 獲取頁面中所有含有ref屬性的DOM元素(如vm.$refs.hello,獲取頁面中含有屬性ref = “hello”的DOM元素,如果有多個元素,那麼只返回最後一個)

生命週期鉤子函數

vue在創建的時候 會有一個週期 例如,需要設置數據監聽、編譯模板、將實例掛載到 DOM 並在數據變化時更新 DOM 等。同時在這個過程中也會運行一些叫做生命週期鉤子的函數,這樣就可以在合適的地方寫下合適的代碼了。

new Vue({
  data: {
    a: 1
  },
  created: function () { // creat函數 就是在實例創建之後執行
    // `this` 指向 vm 實例
    console.log('a is: ' + this.a)
  }
})
// => "a is: 1"

這裏的creat是在實例創建之後執行 還有幾個常用的mounted、updated和destroyed,生命週期鉤子的this上下文指向的是調用他的Vue實例

Vue的生命週期

Vue 實例生命週期

着重注意幾個函數:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed

1.beforeCreate

​ 在這個鉤子函數裏,只是剛開始初始化實例,你拿不到實例裏的任何東西,比如data和methods和事件監聽等。

data: {
    msg: 'linlin'
  },
  methods: {
    getLists(){
      return 'aaa'
    }
  },
  beforeCreate() {
    console.log('beforeCreate',this.msg,this.getLists())
  }

2.created

​ 在實例創建完成後立即調用。在這一步 實例已經完成了以下配置:數據觀測 (data observer),屬性和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el屬性目前不可見。這是最早能拿到實例裏面的數據和方法的一個鉤子函數。應用場景:異步數據的獲取和對實例數據的初始化操作都在這裏面進行。

data: {
    msg: 'linlin',
    imgs: null
  },
methods: {
    getLists(){
      this.$http.get(url).then(res=>{
        this.imgs = res.data.lists
        console.log(this.imgs)
      })
    }
  },
  created() {
    this.getLists()
  }

3.beforeMount

​ 在掛載開始之前被調用: 相關的render函數首次被調用。

​ 無論是created還是beforeMount在他們裏面都拿不到真實的dom元素,如果我們需要拿到dom元素 就需要在Mounted裏面操作 比如繪製一個echarts 需要獲得頁面上的dom元素 就需要在mounted裏面操作;

<div id="app">
      <ul>
        <li v-for="(item,index) in arr" :key="index">{{item}}</li>
      </ul>
    </div>
<script>
let app = new Vue({
 data: {
    arr: [1,2,3]
  },
created() {
    console.log('created',document.querySelectorAll('li').length)
  },
  beforeMount() {
    console.log('beforeMount',document.querySelectorAll('li').length)
  },
  mounted() {
    console.log('mounted',document.querySelectorAll('li').length)
  },
})
</script>

這裏就只有Mounted可以獲取到dom元素的長度 created和beforeMount裏面還未渲染dom元素 arr中的值 自然也不會被循環出來

4.mounted

​ 3點中已經大致的說明了Mounted的內容 但是這裏要注意的是mounted裏面拿到的dom元素只是初始化數據裏面的dom元素 如果需要異步對dom元素進行修改 就需要在updated裏面獲取,應用場景: 初始化數據(data中有的數據)的dom渲染完畢,可以獲取到dom

<div id="app">
    <ul>
      <li v-for="(item,index) in arr" :key="index">{{item}}</li>
    </ul>
 </div>

created() {
    setTimeout(()=>{
      this.arr = [4,5,6,7]
      console.log('created',document.querySelectorAll('li').length)
    })   
  },
  mounted() {
    console.log('mounted',document.querySelectorAll('li').length)
  }

這裏還需要注意的是:$children子組件的獲取也需要在mounted裏

5.beforeUpdate

​ 當數據更新後出發的鉤子函數,這個鉤子函數裏面拿到的是更改之前的數據 虛擬dom重新渲染和打補丁之前被調用 可以在這個函數裏面進一步修改data,這裏修改了不會出發附加的重渲染過程。

6.updated

​ 獲取數據更新後的數據(update是指的Mounted鉤子後包括Mounted的數據更改,在created裏面的數據更改不叫更改,是初始化),這裏不建議進行一步數據得到的dom操作 還是要放到mounted中比較好,updated有一個特點就是隻要相關的數據更改一次 他就會執行一次 和計算屬性類似。應用場景:如果dom操作一來的數據是在異步操作中獲取,並且只有一次數據的更改,也可以說是數據更新完畢:如果對數據更新做一些同意處理在updated鉤子函數中處理即可。

注意:當這個鉤子被調用時,組件DOM的data已經更新,所以你現在可以執行依賴於DOM的操作。但是不要在當前鉤子裏修改當前組件中的data,否則會繼續觸發beforeUpdate、updated這兩個生命週期,進入死循環!

<div id="app">
      <ul>
        <li v-for="(item,index) in arr" :key="index" @click="getAdd">{{item}}</li>
      </ul>
      <div>{{msg}}</div>
    </div>

data: {
    arr: [1,2,3],
  },
  methods: {
    getAdd(){
      this.arr = [4,5,6,7]
    }
  },
  updated() {
    console.log(this.arr) //輸出 4 5 6 7
  }

7.beforeDestroy

​ 實例銷燬之前調用。在這一步,實例仍然完全可用

8.destroyed

​ Vue實例銷燬後調用,調用後,Vue實例指示的所有東西都會解綁,所有的實踐監聽會被移除,所有的子實例也會被銷燬。

​ beforeDestroy和destroyed只能通過手動觸發$destroy來調用

let app = new Vue({
  beforeDestroy() {
    console.log('beforeDestroy')
  },
  destroyed() {
    console.log('destroyed')
  }
})
app.$destroy() //控制檯打印 beforeDestroy destroyed

模板語法

1.Mustache 語法 ({{msg}})

<span>Message: {{ msg }}</span>

這裏的msg就和data中定義的msg相互綁定 data中msg值改變 這裏也會動態改變 如果不想要他改變 就可以使用v-once指令 這個用得很少 基本上不用!

<span v-once>這個將不會改變: {{ msg }}</span>

2.指令

​ 指令 (Directives) 是帶有 v- 前綴的特殊 attribute。指令的值是單個javaScript表達式(v-for例外)。指令主要的功能是當表達式的值發生改變 將其產生的連帶影響 響應式地作用到dom上,例如:這裏see爲true就生成p這個dom 反之則不生成

<p v-if="seen">現在你看到我了</p>

a.參數

一個指令能夠接收一個參數,在指令名稱後以冒號表示。例如 v-bind指令

<a v-bind:href="url">...</a>

這裏的參數是href 告知v-bind該元素的href與url綁定

v-on:click a的點擊事件綁定doSomething方法

<a v-on:click="doSomething">...</a>

b.動態參數

從 2.6.0 開始,可以用方括號括起來的 JavaScript 表達式作爲一個指令的參數:

<a v-bind:[attributeName]="url"> ... </a>

attributeName可以是一個表達式 也可以直接是data中的一個屬性。動態參數預期會求出一個字符串,異常情況下值爲 null。這個特殊的 null 值可以被顯性地用於移除綁定。任何其它非字符串類型的值都將會觸發一個警告。

動態參數表達式有一些語法約束,因爲某些字符,如空格和引號,放在 HTML attribute 名裏是無效的。例如:

<!-- 這會觸發一個編譯警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>

這裏可以使用沒有空格或引號的表達式,或用計算屬性替代這種複雜表達式。

在 DOM 中使用模板時 (直接在一個 HTML 文件裏撰寫模板),還需要避免使用大寫字符來命名鍵名,因爲瀏覽器會把 attribute 名全部強制轉爲小寫:

<!--
在 DOM 中使用模板時這段代碼會被轉換爲 `v-bind:[someattr]`。
除非在實例中有一個名爲“someattr”的 property,否則代碼不會工作。
-->
<a v-bind:[someAttr]="value"> ... </a>

c.修飾符

修飾符 (modifier) 是以半角句號 . 指明的特殊後綴,用於指出一個指令應該以特殊方式綁定。例如,.prevent 修飾符告訴 v-on 指令對於觸發的事件調用 event.preventDefault()

<form v-on:submit.prevent="onSubmit">...</form>

2.縮寫

v- 前綴作爲一種視覺提示,用來識別模板中 Vue 特定的 attribute。當你在使用 Vue.js 爲現有標籤添加動態行爲 (dynamic behavior) 時,v- 前綴很有幫助,然而,對於一些頻繁用到的指令來說,就會感到使用繁瑣。同時,在構建由 Vue 管理所有模板的單頁面應用程序 (SPA - single page application) 時,v- 前綴也變得沒那麼重要了。因此,Vue 爲 v-bindv-on 這兩個最常用的指令,提供了特定簡寫:

v-bind

<!-- 完整語法 -->
<a v-bind:href="url">...</a>

<!-- 縮寫 -->
<a :href="url">...</a>

<!-- 動態參數的縮寫 (2.6.0+) -->
<a :[key]="url"> ... </a>

v-on

<!-- 完整語法 -->
<a v-on:click="doSomething">...</a>

<!-- 縮寫 -->
<a @click="doSomething">...</a>

<!-- 動態參數的縮寫 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>

計算屬性和偵聽器

1.計算屬性

​ 在html中 不要放太多的邏輯代碼 比如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

這個message可以直接放到js中處理 完全可以放入計算屬性中進行計算

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 計算屬性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 實例
      return this.message.split('').reverse().join('')
    }
  }
})

這樣 reversedMessage就會根據message的值改變而改變 message只要改變就會執行計算屬性 計算屬性有緩存 所以這裏如果message的值和之前的一樣 就會直接返回而不是再去計算一次;

計算屬性和方法

上面說到的反轉字符串 在方法中同樣可以做到 只要調用一下下面的方法 Message的值就會改變

// 在組件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

不同的是計算屬性是基於它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們纔會重新求值。這就意味着只要 message 還沒有發生改變,多次訪問 reversedMessage 計算屬性會立即返回之前的計算結果,而不必再次執行函數。如果是方法的話 就會每次都調用 如果是很大的計算量 那麼性能就會大大降低。

計算屬性和watch(偵聽屬性)

偵聽屬性是Vue提供的一種更通用的方式觀察和相應Vue實例上的數據變動。當需要一些數據需要隨着其他數據變動而變動的時候,很容易會濫用watch 在這個時候 通常使用計算屬性會好過偵聽屬性。

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

這裏偵聽了兩個屬性 firstName和lastName來改變fullName的值 使用計算屬性 會更簡單 只要return中的屬性 任何一個發生改變 都會調用

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

計算屬性只有getter,可以自己提供一個setter:

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

現在就可以調用 vm.fullName = 'John Doe' ,setter 會被調用,vm.firstNamevm.lastName 也會相應地被更新。

2.偵聽器

​ 雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。這就是爲什麼 Vue 通過 watch 選項提供了一個更通用的方法,來響應數據的變化。當需要在數據變化時執行異步或開銷較大的操作時,這個方式是最有用的。

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<!-- 因爲 AJAX 庫和通用工具的生態已經相當豐富,Vue 核心代碼沒有重複 -->
<!-- 提供這些功能以保持精簡。這也可以讓你自由選擇自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
  },
  watch: {
    // 如果 `question` 發生改變,這個函數就會運行
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.debouncedGetAnswer()
    }
  },
  created: function () {
    // `_.debounce` 是一個通過 Lodash 限制操作頻率的函數。
    // 在這個例子中,我們希望限制訪問 yesno.wtf/api 的頻率
    // AJAX 請求直到用戶輸入完畢纔會發出。想要了解更多關於
    // `_.debounce` 函數 (及其近親 `_.throttle`) 的知識,
    // 請參考:https://lodash.com/docs#debounce
    this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
  },
  methods: {
    getAnswer: function () {
      if (this.question.indexOf('?') === -1) {
        this.answer = 'Questions usually contain a question mark. ;-)'
        return
      }
      this.answer = 'Thinking...'
      var vm = this
      axios.get('https://yesno.wtf/api')
        .then(function (response) {
          vm.answer = _.capitalize(response.data.answer)
        })
        .catch(function (error) {
          vm.answer = 'Error! Could not reach the API. ' + error
        })
    }
  }
})
</script>

Class與Style綁定

綁定HTML Class

對象語法

我們可以傳給 v-bind:class 一個對象,以動態地切換 class:

<div v-bind:class="{ active: isActive }"></div>

這裏class是否存在看isActive的值爲true還是false

<div
  class="static"
  v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>

這裏是兩種寫法,isAcitve如果爲true class=“static acitve” 如果hasError也爲true class=“static active text-danger”

數組語法

<div v-bind:class="[activeClass, errorClass]"></div>
data: {  activeClass: 'active',  errorClass: 'text-danger' }

class = “active text-danger”

還可以用表達式

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

數組語法中套對象語法

<div v-bind:class="[{ active: isActive }, errorClass]"></div>

用在組件上

當在一個自定義組件上使用 class property 時,這些 class 將被添加到該組件的根元素上面。這個元素上已經存在的 class 不會被覆蓋。

例如,如果你聲明瞭這個組件

Vue.component('my-component', {
  template: '<p class="foo bar">Hi</p>'
})

然後在使用它的時候添加一些 class:

<my-component class="baz boo"></my-component>

渲染的html爲

<p class="foo bar baz boo">Hi</p>

對於帶數據綁定 class 也同樣適用:

<my-component v-bind:class="{ active: isActive }"></my-component>

isActive 爲 truthy[1] 時,HTML 將被渲染成爲:

<p class="foo bar active">Hi</p>

綁定內聯樣式

對象語法

v-bind:style 的對象語法十分直觀——看着非常像 CSS,但其實是一個 JavaScript 對象。CSS property 名可以用駝峯式 (camelCase) 或短橫線分隔 (kebab-case,記得用引號括起來) 來命名:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
  activeColor: 'red',
  fontSize: 30
}

直接綁定到一個樣式對象通常更好,這會讓模板更清晰:

<div v-bind:style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

同樣的,對象語法常常結合返回對象的計算屬性使用。

數組語法

v-bind:style 的數組語法可以將多個樣式對象應用到同一個元素上:

<div v-bind:style="[baseStyles, overridingStyles]"></div>

這種在標籤上加style是不推薦使用的

自動添加前綴

v-bind:style 使用需要添加瀏覽器引擎前綴的 CSS property 時,如 transform,Vue.js 會自動偵測並添加相應的前綴。

條件渲染

v-if 控制標籤是否生成

<h1 v-if="awesome">Vue is awesome!</h1>

與else搭配

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

與v-else-if搭配

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

用key管理可複用的元素

Vue 會盡可能高效地渲染元素,通常會複用已有元素而不是從頭開始渲染。這麼做除了使 Vue 變得非常快之外,還有其它一些好處。例如,如果你允許用戶在不同的登錄方式之間切換:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address">
</template>

那麼在上面的代碼中切換 loginType 將不會清除用戶已經輸入的內容。因爲兩個模板使用了相同的元素,`` 不會被替換掉——僅僅是替換了它的 placeholder

這樣也不總是符合實際需求,所以 Vue 爲你提供了一種方式來表達“這兩個元素是完全獨立的,不要複用它們”。只需添加一個具有唯一值的 key attribute 即可:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

v-show 和v-if

v-show是生成元素 給一個隱藏屬性(display:none)。 注意,v-show 不支持 元素,也不支持v-else`。

<h1 v-show="ok">Hello!</h1>

v-if 是“真正”的條件渲染,因爲它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷燬和重建。

v-if 也是惰性的:如果在初始渲染時條件爲假,則什麼也不做——直到條件第一次變爲真時,纔會開始渲染條件塊。

相比之下,v-show 就簡單得多——不管初始條件是什麼,元素總是會被渲染,並且只是簡單地基於 CSS 進行切換。

一般來說,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在運行時條件很少改變,則使用 v-if 較好。

列表渲染

我們可以用 v-for 指令基於一個數組來渲染一個列表。v-for 指令需要使用 item in items 形式的特殊語法,其中 items 是源數據數組,而 item 則是被迭代的數組元素的別名

<ul id="example-1">
  <li v-for="item in items" :key="item.message">
    {{ item.message }}
  </li>
</ul>
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

結果爲 Foo Bar

v-for 塊中,我們可以訪問所有父作用域的 property。v-for 還支持一個可選的第二個參數,即當前項的索引。

<ul id="example-2">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
var example2 = new Vue({
  el: '#example-2',
  data: {
    parentMessage: 'Parent',
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

結果爲 Parent - 0 - Foo Parent - 1 -Bar

也可以用 of 替代 in 作爲分隔符,因爲它更接近 JavaScript 迭代器的語法:

<div v-for="item of items"></div>

在v-for中循環對象

<ul id="v-for-object" class="demo">
  <li v-for="value in object">
    {{ value }}
  </li>
</ul>
new Vue({
  el: '#v-for-object',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})

這裏循環出object中的值

也可以提供第二個的參數爲 property 名稱 (也就是鍵名):

<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
</div>

還可以用第三個參數作爲索引:

<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
</div>

維護狀態

當 Vue 正在更新使用 v-for 渲染的元素列表時,它默認使用“就地更新”的策略。如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,而是就地更新每個元素,並且確保它們在每個索引位置正確渲染。這個類似 Vue 1.x 的 track-by="$index"

這個默認的模式是高效的,但是只適用於不依賴子組件狀態或臨時 DOM 狀態 (例如:表單輸入值) 的列表渲染輸出

爲了給 Vue 一個提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要爲每項提供一個唯一 key attribute:

<div v-for="item in items" v-bind:key="item.id">
  <!-- 內容 -->
</div>

建議儘可能在使用 v-for 時提供 key attribute,除非遍歷輸出的 DOM 內容非常簡單,或者是刻意依賴默認行爲以獲取性能上的提升。

因爲它是 Vue 識別節點的一個通用機制,key 並不僅與 v-for 特別關聯。後面我們將在指南中看到,它還具有其它用途。

數組更新檢測

變更方法

Vue 將被偵聽的數組的變更方法進行了包裹,所以它們也將會觸發視圖更新。這些被包裹過的方法包括:push() pop() shift() unshift() splice() sort() reverse()

替換數組

變更方法,顧名思義,會變更調用了這些方法的原始數組。相比之下,也有非變更方法,例如 filter()concat()slice()。它們不會變更原始數組,而總是返回一個新數組。當使用非變更方法時,可以用新數組替換舊數組:

example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

你可能認爲這將導致 Vue 丟棄現有 DOM 並重新渲染整個列表。幸運的是,事實並非如此。Vue 爲了使得 DOM 元素得到最大範圍的重用而實現了一些智能的啓發式方法,所以用一個含有相同元素的數組去替換原來的數組是非常高效的操作。

顯示過濾/排序後的結果

使用計算屬性

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers: function () {
    return this.numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

在計算屬性不適用的情況下 (例如,在嵌套 v-for 循環中) 你可以使用一個方法:

<ul v-for="set in sets">
  <li v-for="n in even(set)">{{ n }}</li>
</ul>
data: {
  sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
  even: function (numbers) {
    return numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

在v-for裏面使用值範圍

v-for 也可以接受整數。在這種情況下,它會把模板重複對應次數。

<div>
  <span v-for="n in 10">{{ n }} </span>
</div>

這樣也能輸出 1 2 3 4 5 6 7 8 9 10

上使用v-for

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-for和v-if一起使用

當它們處於同一節點,v-for 的優先級比 v-if 更高 因此循環會挨個判斷 是否生成這個Li

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

而如果你的目的是有條件地跳過循環的執行,那麼可以將 v-if 置於外層元素 (或 `) 上。如:

<ul v-if="todos.length">
  <li v-for="todo in todos">
    {{ todo }}
  </li>
</ul>
<p v-else>No todos left!</p>

組件上使用v-for

注意:2.2.0+ 的版本里,當在組件上使用 v-for 時,key 現在是必須的

<my-component v-for="item in items" :key="item.id"></my-component>

使用prop傳值給組件

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index"
  v-bind:key="item.id"
></my-component>

todo列表實例

<div id="todo-list-example">
  <form v-on:submit.prevent="addNewTodo">
    <label for="new-todo">Add a todo</label>
    <input
      v-model="newTodoText"
      id="new-todo"
      placeholder="E.g. Feed the cat"
    >
    <button>Add</button>
  </form>
  <ul>
    <li
      is="todo-item"
      v-for="(todo, index) in todos"
      v-bind:key="todo.id"
      v-bind:title="todo.title"
      v-on:remove="todos.splice(index, 1)"
    ></li>
  </ul>
</div>

注意這裏的 is="todo-item" attribute。這種做法在使用 DOM 模板時是十分必要的,因爲在 元素內只有 元素會被看作有效內容。這樣做實現的效果與 `` 相同,但是可以避開一些潛在的瀏覽器解析錯誤。

Vue.component('todo-item', {
  template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  ',
  props: ['title']
})

new Vue({
  el: '#todo-list-example',
  data: {
    newTodoText: '',
    todos: [
      {
        id: 1,
        title: 'Do the dishes',
      },
      {
        id: 2,
        title: 'Take out the trash',
      },
      {
        id: 3,
        title: 'Mow the lawn'
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function () {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})

事件處理

監聽事件

v-on監聽dom事件

<div id="example-1">
  <button v-on:click="counter += 1">Add 1</button>
  <p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
  el: '#example-1',
  data: {
    counter: 0
  }
})

事件處理方法

<div id="example-2">
  <!-- `greet` 是在下面定義的方法名 -->
  <button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
  el: '#example-2',
  data: {
    name: 'Vue.js'
  },
  // 在 `methods` 對象中定義方法
  methods: {
    greet: function (event) {
      // `this` 在方法裏指向當前 Vue 實例
      alert('Hello ' + this.name + '!')
      // `event` 是原生 DOM 事件
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
})

// 也可以用 JavaScript 直接調用方法
example2.greet() // => 'Hello Vue.js!'

內聯處理器中的方法

<div id="example-3">
  <button v-on:click="say('hi')">Say hi</button>
  <button v-on:click="say('what')">Say what</button>
</div>
new Vue({
  el: '#example-3',
  methods: {
    say: function (message) {
      alert(message)
    }
  }
})

有時也需要在內聯語句處理器中訪問原始的 DOM 事件。可以用特殊變量 $event 把它傳入方法:

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>
methods: {
  warn: function (message, event) {
    // 現在我們可以訪問原生事件對象
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

事件修飾符

.stop .prevent .capture .self .once .passive .once .passive

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

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

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

<!-- 只有修飾符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件監聽器時使用事件捕獲模式 -->
<!-- 即內部元素觸發的事件先在此處理,然後才交由內部元素進行處理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只當在 event.target 是當前元素自身時觸發處理函數 -->
<!-- 即事件不是從內部元素觸發的 -->
<div v-on:click.self="doThat">...</div>

<!-- 點擊事件將只會觸發一次 -->
<a v-on:click.once="doThis"></a>

<!-- 滾動事件的默認行爲 (即滾動行爲) 將會立即觸發 -->
<!-- 而不會等待 `onScroll` 完成  -->
<!-- 這其中包含 `event.preventDefault()` 的情況 -->
<div v-on:scroll.passive="onScroll">...</div>

按鍵修飾符

<!-- 只有在 `key` 是 `Enter` 時調用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

<input v-on:keyup.page-down="onPageDown">

按鍵碼

.enter .tab .delete(刪除和退格鍵) .esc .space .up .down .left .right

<input v-on:keyup.13="submit">

有一些按鍵 (.esc 以及所有的方向鍵) 在 IE9 中有不同的 key 值, 如果你想支持 IE9,這些內置的別名應該是首選。

還可以通過全局 config.keyCodes 對象自定義按鍵修飾符別名:

// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

系統修飾鍵

.ctrl .alt .shift .meta

<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

鼠標按鈕修飾符

.left .right .middle

這些修飾符會限制處理函數僅響應特定的鼠標按鈕。

表單輸入綁定

使用v-model實現雙向綁定

文本

<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

多行文本

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

複選框

<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

多個複選框,綁定到同一個數組:

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
new Vue({
  el: '...',
  data: {
    checkedNames: []
  }
})

單選按鈕

<div id="example-4">
  <input type="radio" id="one" value="One" v-model="picked">
  <label for="one">One</label>
  <br>
  <input type="radio" id="two" value="Two" v-model="picked">
  <label for="two">Two</label>
  <br>
  <span>Picked: {{ picked }}</span>
</div>
new Vue({
  el: '#example-4',
  data: {
    picked: ''
  }
})

選擇框

<div id="example-5">
  <select v-model="selected">
    <option disabled value="">請選擇</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>
new Vue({
  el: '...',
  data: {
    selected: ''
  }
})

v-for 渲染的動態選項:

<select v-model="selected">
  <option v-for="option in options" v-bind:value="option.value">
    {{ option.text }}
  </option>
</select>
<span>Selected: {{ selected }}</span>
new Vue({
  el: '...',
  data: {
    selected: 'A',
    options: [
      { text: 'One', value: 'A' },
      { text: 'Two', value: 'B' },
      { text: 'Three', value: 'C' }
    ]
  }
})

值綁定

<!-- 當選中時,`picked` 爲字符串 "a" -->
<input type="radio" v-model="picked" value="a">

<!-- `toggle` 爲 true 或 false -->
<input type="checkbox" v-model="toggle">

<!-- 當選中第一個選項時,`selected` 爲字符串 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>

複選框綁定值

<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no"
>
// 當選中時
vm.toggle === 'yes'
// 當沒有選中時
vm.toggle === 'no'

單選按鈕綁定

<input type="radio" v-model="pick" v-bind:value="a">
// 當選中時
vm.pick === vm.a

選擇匡的選項

<select v-model="selected">
    <!-- 內聯對象字面量 -->
  <option v-bind:value="{ number: 123 }">123</option>
</select>
// 當選中時
typeof vm.selected // => 'object'
vm.selected.number // => 123

修飾符

.lazy

<!-- 在“change”時而非“input”時更新 -->
<input v-model.lazy="msg">

.number

<!-- 輸入值轉爲數值類型 -->
<input v-model.number="age" type="number">

.trim

<!-- 清除首位空格 -->
<input v-model.trim="msg">

組件基礎

實例:

// 定義一個名爲 button-counter 的新組件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

使用標籤即可使用組件

<div id="components-demo">
  <button-counter></button-counter>
</div>

可以多次複用

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

組件的data必須是一個函數

data: function () {
  return {
    count: 0
  }
}

組件全局註冊

Vue.component('my-component-name', {
  // ... options ...
})

父組件傳值到子組件

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

這裏props的值是title 傳值到組件內就通過title

傳遞數組對象

new Vue({
  el: '#blog-post-demo',
  data: {
    posts: [
      { id: 1, title: 'My journey with Vue' },
      { id: 2, title: 'Blogging with Vue' },
      { id: 3, title: 'Why Vue is so fun' }
    ]
  }
})
<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
></blog-post>

單個根元素

每個組件都只有一個根元素

<div class="blog-post">
  <h3>{{ title }}</h3>
  <div v-html="content"></div>
</div>

組件註冊

全局註冊

主要頁面都會使用到的基礎組件使用全局註冊

Vue.component('my-component-name', { /* ... */ })

這樣就可以直接使用 來使用組件

局部註冊

普通的js中

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

這樣就可以使用這兩個組件了 局部註冊的組件 在其子組件裏面是不可用的 僅註冊處使用

ES6

直接使用import引入組件的Vue文件 現在都用這個

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

模塊系統

在模塊系統中局部註冊

和上面的局部註冊一樣 把兩個組件註冊到ComponentB中 這樣就可以在ComponentB中使用註冊的組件。

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,
    ComponentC
  },
  // ...
}

Prop

注意js裏面和html裏面的命名

js中

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

html中

<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>

postTitle 對應 post-title

props類型

props中的屬性都可以定義類型

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

傳遞靜態或動態Prop

傳靜態: 寫死了

<blog-post title="My journey with Vue"></blog-post>

傳動態:傳遞當前vue中的data中的屬性值 屬性值改變就跟着變化

<!-- 動態賦予一個變量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

<!-- 動態賦予一個複雜表達式的值 -->
<blog-post
  v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

可以傳遞數字、數組、對象等等

單向數據流

意思是隻能父組件傳給子組件 子組件傳值給父組件 通常使用$emit

Prop驗證

驗證傳入的值 也就是通過prop類型來驗證 還有required可以驗證必填 如果沒有值 還可以給一個默認值。type也做了傳入的類型檢查

Vue.component('my-component', {
  props: {
    // 基礎的類型檢查 (`null` 和 `undefined` 會通過任何類型驗證)
    propA: Number,
    // 多個可能的類型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 帶有默認值的數字
    propD: {
      type: Number,
      default: 100
    },
    // 帶有默認值的對象
    propE: {
      type: Object,
      // 對象或數組默認值必須從一個工廠函數獲取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗證函數
    propF: {
      validator: function (value) {
        // 這個值必須匹配下列字符串中的一個
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

自定義事件

事件名稱

this.$emit('myEvent')

​ 父頁面上

<!-- 沒有效果 -->
<my-component v-on:my-event="doSomething"></my-component>

​ 在父頁面的js裏面 寫doSomething()方法 進行操作字組件傳遞到父組件

自定義組件的v-model

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

組件上使用

<base-checkbox v-model="lovingVue"></base-checkbox>

這裏v-model就使用了props的checked 因爲model裏面的Prop是checked 這個對象裏面的value和Props裏面的那個屬性一樣就會使用哪個。

組件上監聽原生事件

使用.native修飾符

<base-input v-on:focus.native="onFocus"></base-input>

.sync修飾符

子組件

this.$emit('update:title', newTitle)

父組件

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

子組件使用$emit 父組件中通過一個方法返回新值再給子組件 通常父組件是縮寫成

<text-document v-bind:title.sync="doc.title"></text-document>

插槽

插槽其實 就是在組件裏面佔一個位置 這樣在父組件中 可以自定義一些標籤 放到這個插槽的位置

比如定義一個

<button type="submit">
  <slot>Submit</slot>
</button>
<submit-button></submit-button>

這樣渲染出來就是

<button type="submit">
  Submit
</button>

比如一個組件

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

這樣們引用他

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</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>
</base-layout>

這個組件標籤加載的內容 就會放到插槽的位置

動態組件和異步組件

動態組件

<!-- 失活的組件將會被緩存!-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

用包裹組件

這個組件就會緩存之前的操作 如果是頁籤切換 那麼互相切換 都會保存之前的操作狀態

異步組件

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回調傳遞組件定義
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

在大型應用中,我們可能需要將應用分割成小一些的代碼塊,並且只在需要的時候才從服務器加載一個模塊。爲了簡化,Vue 允許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件需要被渲染的時候纔會觸發該工廠函數,且會把結果緩存起來供未來重渲染。

d 這個對象裏面的value和Props裏面的那個屬性一樣就會使用哪個。

組件上監聽原生事件

使用.native修飾符

<base-input v-on:focus.native="onFocus"></base-input>

.sync修飾符

子組件

this.$emit('update:title', newTitle)

父組件

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

子組件使用$emit 父組件中通過一個方法返回新值再給子組件 通常父組件是縮寫成

<text-document v-bind:title.sync="doc.title"></text-document>

插槽

插槽其實 就是在組件裏面佔一個位置 這樣在父組件中 可以自定義一些標籤 放到這個插槽的位置

比如定義一個

<button type="submit">
  <slot>Submit</slot>
</button>
<submit-button></submit-button>

這樣渲染出來就是

<button type="submit">
  Submit
</button>

比如一個組件

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

這樣們引用他

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</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>
</base-layout>

這個組件標籤加載的內容 就會放到插槽的位置

動態組件和異步組件

動態組件

<!-- 失活的組件將會被緩存!-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

用包裹組件

這個組件就會緩存之前的操作 如果是頁籤切換 那麼互相切換 都會保存之前的操作狀態

異步組件

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回調傳遞組件定義
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

在大型應用中,我們可能需要將應用分割成小一些的代碼塊,並且只在需要的時候才從服務器加載一個模塊。爲了簡化,Vue 允許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件需要被渲染的時候纔會觸發該工廠函數,且會把結果緩存起來供未來重渲染。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章