1?列表渲染
1.1?在 v-for 里使用數組
????????v-for 指令可以實現基于一個數組來渲染一個列表。v-for
?指令需要使用?item in items
?形式的特殊語法,其中?items
?是源數據數組,而?item
?則是被迭代的數組元素的別名。
- 在 v-for 塊中,我們可以訪問所有父作用域的?
property
- 第一個參數?
item
?則是被迭代的數組元素的別名。 - 第二個參數,即當前項的索引?
index
?,是可選的。
<template><view><view v-for="(item, index) in items">{{ parentMessage }} - {{ index }} - {{ item.message }}</view></view></template><script>export default {data() {return {parentMessage: 'Parent',items: [{ message: 'Foo' },{ message: 'Bar' }]}}}</script>
1.2?在 v-for 里使用對象
????????你也可以用 v-for 來遍歷一個對象的?property
。
- 第一個參數?
value
?是被迭代的對象元素的屬性值。 - 第二個參數為?
property
?名稱 (也就是鍵名)。 - 第三個參數作為索引。
<template><view><view v-for="(value, name, index) in object">{{ index }}. {{ name }}: {{ value }}</view></view></template><script>export default {data() {return {object: {title: 'How to do lists in Vue',author: 'Jane Doe',publishedAt: '2021-05-10'}}}}</script>
????????在遍歷對象時,會按?Object.keys()
?的結果遍歷,但是不能保證它在不同?JavaScript
?引擎下的結果都一致。
1.3?列表渲染分組
????????類似于?v-if
,你也可以利用帶有?v-for
?的?template
?來循環渲染一段包含多個元素的內容。比如:
<template v-for="item in items"><view>{{ item.message }}</view><view class="divider" role="presentation"></view></template>
1.4?維護狀態
????????當?Vue
?正在更新使用?v-for
?渲染的元素列表時,它默認使用“就地更新”的策略。如果數據項的順序被改變,Vue
?將不會移動?DOM
?元素來匹配數據項的順序,而是就地更新每個元素,并且確保它們在每個索引位置正確渲染。
????????這個默認的模式是高效的,但是只適用于不依賴子組件狀態或臨時 DOM 狀態 (例如:表單輸入值) 的列表渲染輸出。為了給?Vue
?一個提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一?key
?attribute:
<view v-for="item in items" :key="item.id"><!-- content --></view>
????????建議盡可能在使用?v-for
?時提供?key
?attribute,除非遍歷輸出的 DOM 內容非常簡單,或者是刻意依賴默認行為以獲取性能上的提升。
- 如果不使用 key,Vue 會使用一種最大限度減少動態元素并且盡可能的嘗試就地修改/復用相同類型元素的算法。
- 而使用 key 時,它會基于 key 的變化重新排列元素順序,并且會移除/銷毀 key 不存在的元素。
- 有相同父元素的子元素必須有獨特的 key。重復的 key 會造成渲染錯誤。
????????不要使用對象或數組之類的非基本類型值作為 v-for 的 key。請用字符串或數值類型的值。如不提供 :key,會報一個?warning
, 如果明確知道該列表是靜態,或者不必關注其順序,可以選擇忽略。
<template><view><!-- array 中 item 的某個 property --><view v-for="(item,index) in objectArray" :key="item.id">{{index +':'+ item.name}}</view><!-- item 本身是一個唯一的字符串或者數字時,可以使用 item 本身 --><view v-for="(item,index) in stringArray" :key="item">{{index +':'+ item}}</view></view></template><script>export default {data () {return {objectArray:[{id:0,name:'li ming'},{id:1,name:'wang peng'}],stringArray:['a','b','c']}}}</script>
1.5?注意事項
????????小程序端數據為差量更新方式,由于小程序不支持刪除對象屬性,使用的設置值為 null 的方式替代,導致遍歷時可能出現不符合預期的情況,需要自行過濾一下值為 null 的數據
1.6?結合?<template v-for>
????????在Vue3
中,key
?則應該被設置在?<template>
?標簽上
<template v-for="item in list" :key="item.id"><view>...</view><text>...</text></template>
????????類似地,當使用?<template v-for>
?時存在使用?v-if
?的子節點,key
?應改為設置在?<template>
?標簽上。
<template v-for="item in list" :key="item.id"><view v-if="item.isVisible">...</view><view v-else>...</view></template>
1.7?在組件上使用 v-for
????????在自定義組件上,你可以像在任何普通元素上一樣使用?v-for
?。
<my-component v-for="item in items" :key="item.id"></my-component>
????????當在組件上使用 v-for 時,key是必須的。
1.8?v-for 與 v-if 一同使用
????????當它們處于同一節點,v-if
?的優先級比?v-for
?更高,這意味著?v-if
?將沒有權限訪問?v-for
?里的變量:
<!-- 這將引發錯誤,因為未在實例上定義屬性“todo” --><view v-for="todo in todos" v-if="!todo.isComplete">{{ todo }}</view>
????????可以把?v-for
?移動到?template
?標簽中來修正:
<template v-for="todo in todos"><view v-if="!todo.isComplete">{{ todo }}</view></template>
2?事件處理
2.1?監聽事件
????????我們可以使用?v-on
?指令 (通常縮寫為 @ 符號,下文簡稱為:@事件) 來監聽 DOM 事件,并在觸發事件時執行一些?JavaScript
。用法為?v-on:click="methodName"
?或使用快捷方式?@click="methodName"
?(uni-app里一般都使用@縮寫方式)指令的值,字符串里直接寫js。比如下面的counter += 1
就是一段js。
<template><view><button @click="counter += 1">Add 1</button><text>The button above has been clicked {{ counter }} times.</text></view></template><script>export default {data() {return {counter:0}}}</script>
2.2?事件處理方法
????????然而許多事件處理邏輯會更為復雜,所以直接把?JavaScript
?代碼寫在組件屬性值里是不可行的。因此@事件還可以接收一個需要調用的方法名稱。
<template><view><!-- `greet` 是在下面定義的方法名 --><button @click="greet">Greet</button></view></template><script>export default {data() {return {name: 'Vue.js'}},// 在 `methods` 對象中定義方法methods: {greet(event){// `event` 是原生 DOM 事件console.log(event);uni.showToast({title: 'Hello ' + this.name + '!'});}}}</script>
2.3?內聯處理器中的方法
????????除了直接綁定到一個方法,也可以在內聯?JavaScript
?語句中調用方法:
<template><view><button @click="say('hi')">Say hi</button><button @click="say('what')">Say what</button></view></template><script>export default {methods: {say(message) {uni.showToast({title: message});}}}</script>
????????有時也需要在內聯語句處理器中訪問原始的 DOM 事件。可以用特殊變量?$event
?把它傳入方法:
<template><view><button @click="warn('Form cannot be submitted yet.', $event)">Submit</button></view></template><script>export default {methods: {warn(message, event) {// 現在我們可以訪問原生事件對象if (event) {//可訪問 event.target等原生事件對象console.log("event: ",event);}uni.showToast({title: message});}}}</script>
2.4?多事件處理器
????????事件處理程序中可以有多個方法,這些方法由逗號運算符分隔:
<template><view><!-- 這兩個 one() 和 two() 將執行按鈕點擊事件 --><button @click="one($event); two($event)">Submit</button></view></template><script>export default {methods: {one(event) {// first handler logic...console.log("event1: ",event);},two(event) {// second handler logic...console.log("event2: ",event);}}}</script>
2.5?事件修飾符
????????修飾符 (modifier) 是以半角句號 . 指明的特殊后綴,用于指出一個指令應該以特殊方式綁定。例如,.prevent
?修飾符告訴 @事件對于觸發的事件調用?event.preventDefault()。
@事件(v-on)提供了事件修飾符:
.stop
: 各平臺均支持, 使用時會阻止事件冒泡,在非 H5 端同時也會阻止事件的默認行為.prevent
: 僅在 H5 平臺支持.capture
: 僅在 H5 平臺支持.self
: 僅在 H5 平臺支持.once
: 僅在 H5 平臺支持.passive
: 僅在 H5 平臺支持
<!-- 阻止單擊事件繼續傳播 --><view @click.stop="doThis"></view>
????????使用修飾符時,順序很重要;相應的代碼會以同樣的順序產生。因此,用?@click.prevent.self
?會阻止所有的點擊,而?@click.self.prevent
?只會阻止對元素自身的點擊。
- 為兼容各端,事件需使用?@?的方式綁定,請勿使用小程序端的?
bind
?和?catch
?進行事件綁定;也不能在 JS 中使用event.preventDefault()
和event.stopPropagation()
方法。 - 若需要禁止蒙版下的頁面滾動,可使用?
@touchmove.stop.prevent="moveHandle"
,moveHandle
?可以用來處理?touchmove
?的事件,也可以是一個空函數。
<view class="mask" @touchmove.stop.prevent="moveHandle"></view>
- 按鍵修飾符:
uni-app
?運行在手機端,沒有鍵盤事件,所以不支持按鍵修飾符。
????????使用 v-on 或 @ 有幾個好處
- 掃一眼?
template
?模板便能輕松定位在?JavaScript
?代碼里對應的方法。 - 因為你無須在?
JavaScript
?里手動綁定事件,你的?ViewModel
?代碼可以是非常純粹的邏輯,和?DOM
?完全解耦,更易于測試。 - 當一個?
ViewModel
?被銷毀時,所有的事件處理器都會自動被刪除。你無須擔心如何清理它們。
2.6?事件映射表
// 事件映射表,左側為 WEB 事件,右側為 ``uni-app`` 對應事件{click: 'tap',touchstart: 'touchstart',touchmove: 'touchmove',touchcancel: 'touchcancel',touchend: 'touchend',tap: 'tap',longtap: 'longtap', //推薦使用longpress代替input: 'input',change: 'change',submit: 'submit',blur: 'blur',focus: 'focus',reset: 'reset',confirm: 'confirm',columnchange: 'columnchange',linechange: 'linechange',error: 'error',scrolltoupper: 'scrolltoupper',scrolltolower: 'scrolltolower',scroll: 'scroll'}
3?表單輸入綁定
3.1?v-model
????????你可以用 v-model 指令在表單?input
、textarea
?及?select
?元素上創建雙向數據綁定。它會根據控件類型自動選取正確的方法來更新元素。盡管有些神奇,但?v-model
?本質上不過是語法糖。它負責監聽用戶的輸入事件以更新數據,并對一些極端場景進行一些特殊處理。
????????v-model 會忽略所有表單元素的?value
、checked
、selected
?attribute 的初始值而總是將 Vue 實例的數據作為數據來源。你應該通過 JavaScript 在組件的 data 選項中聲明初始值。
????????在下面的示例中,輸入框通過v-model
綁定了message
,用戶在輸入框里輸入內容時,這個內容會實時賦值給message
。當然在代碼里為message
賦值也會實時同步到界面上input里。這就是雙向綁定。
<template><view><input v-model="message" placeholder="edit me"><text>Message is: {{ message }}</text></view></template><script>export default {data() {return {message:""}}}</script>
3.2?uni-app表單組件
- H5 的?
select
?標簽用?picker
?組件進行代替
<template><view><picker @change="bindPickerChange" :value="index" :range="array"><view class="picker">當前選擇:{{array[index]}}</view></picker></view></template><script>export default {data() {return {index: 0,array: ['A', 'B', 'C']}},methods: {bindPickerChange(e) {console.log(e)this.index = e.detail.value}}}</script>
- 表單元素?
radio
?用?radio-group
?組件進行代替
<template><view><radio-group class="radio-group" @change="radioChange"><label class="radio" v-for="(item, index) in items" :key="item.name"><radio :value="item.name" :checked="item.checked" /> {{item.value}}</label></radio-group></view></template><script>export default {data() {return {items: [{name: 'USA',value: '美國'},{name: 'CHN',value: '中國',checked: 'true'},{name: 'BRA',value: '巴西'},{name: 'JPN',value: '日本'},{name: 'ENG',value: '英國'},{name: 'TUR',value: '法國'}]}},methods: {radioChange(e) {console.log('radio發生change事件,攜帶value值為:', e.detail.value)}}}</script>
4?計算屬性和偵聽器
4.1?計算屬性computed
????????每一個計算屬性都包含一個?getter
?函數和一個?setter
函數 ,默認是利用?getter
?函數來讀取。所有?getter
?和?setter
?函數的?this
?上下文自動地綁定為 Vue 實例。
4.1.1?計算屬性的 getter
????????模板內的表達式非常便利,但是設計它們的初衷是用于簡單運算的。在模板中放入太多的邏輯會讓模板過重且難以維護。例如,有一個嵌套數組對象:
data() {return {author: {name: 'John Doe',books: ['Vue 2 - Advanced Guide','Vue 3 - Basic Guide','Vue 4 - The Mystery']}}}
????????我們想根據 author 是否已經有一些書來顯示不同的消息,可以使用模板內的表達式
<view><view>Has published books:</view><view>{{ author.books.length > 0 ? 'Yes' : 'No' }}</view></view>
????????此時,模板不再是簡單的和聲明性的。你必須先看一下它,然后才能意識到它執行的計算取決于 author.books。如果要在模板中多次包含此計算,則問題會變得更糟。所以,對于任何包含響應式數據的復雜邏輯,你都應該使用計算屬性。
<template><view><view>OHas published books:</view><view>{{ publishedBooksMessage }}</view></view></template><script>export default {data() {return {author: {name: 'John Doe',books: ['Vue 2 - Advanced Guide','Vue 3 - Basic Guide','Vue 4 - The Mystery']}}},computed: {// 計算屬性的 getterpublishedBooksMessage() {// `this` points to the vm instancereturn this.author.books.length > 0 ? 'Yes' : 'No'}}}</script>
????????這里聲明了一個計算屬性?publishedBooksMessage
。嘗試更改應用程序?data
?中?books
?數組的值,你將看到?publishedBooksMessage
?如何相應地更改。
????????你可以像普通屬性一樣將數據綁定到模板中的計算屬性。Vue 知道?publishedBookMessage
?依賴于?author.books
,因此當?author.books
?發生改變時,所有依賴?publishedBookMessage
?綁定也會更新。而且最妙的是我們已經聲明的方式創建了這個依賴關系:計算屬性的 getter 函數沒有副作用,這使得更易于測試和理解。
????????計算屬性還可以依賴多個 Vue 實例的數據,只要其中任一數據變化,計算屬性就會重新執行,視圖也會更新。
4.1.2?計算屬性的 setter
????????計算屬性默認只有?getter
,不過在需要時你也可以提供一個?setter
, 當手動修改計算屬性的值時,就會觸發?setter
?函數,執行一些自定義的操作。
<template><view><view>{{ fullName }}</view></view></template><script>export default {data() {return {firstName: 'Foo',lastName: 'Bar'}},computed: {fullName: {// getterget(){return this.firstName + ' ' + this.lastName},// setterset(newValue){var names = newValue.split(' ')this.firstName = names[0]this.lastName = names[names.length - 1]}}}}</script>
????????現在再運行?fullName = 'John Doe'
?時,setter
?會被調用,firstName
?和?lastName
?也會相應地被更新。
????????getter與setter區別
- get:通過設置get方法可以得到fullName的新值。
- set:通過set的方法,設置一個值(newValue)來改變fullName相關聯的值,引起fullName重新的計算,相應的頁面上fullName也會發生改變成新的內容。
4.2?計算屬性緩存 vs 方法
????????我們可以通過在表達式中調用方法來達到同樣的效果:
<template><view><view>{{ calculateBooksMessage() }}</view></view></template><script>export default {data() {return {author: {name: 'John Doe',books: ['Vue 2 - Advanced Guide','Vue 3 - Basic Guide','Vue 4 - The Mystery']}}},methods: {calculateBooksMessage() {return this.author.books.length > 0 ? 'Yes' : 'No'}}}</script>
????????我們可以將同一函數定義為一個方法而不是一個計算屬性。兩種方式的最終結果確實是完全相同的。然而,不同的是計算屬性是基于它們的反應依賴關系緩存的。
????????計算屬性只在相關響應式依賴發生改變時它們才會重新求值。這就意味著只要?author.books
?還沒有發生改變,多次訪問?publishedBookMessage
?計算屬性會立即返回之前的計算結果,而不必再次執行函數。這也同樣意味著下面的計算屬性將不再更新,因為?Date.now ()
?不是響應式依賴:
computed: {now(){return Date.now()}}
????????相比之下,每當觸發重新渲染時,調用方法將總會再次執行函數。我們為什么需要緩存?
????????假設我們有一個性能開銷比較大的計算屬性?list
,它需要遍歷一個巨大的數組并做大量的計算。然后我們可能有其他的計算屬性依賴于?list
。如果沒有緩存,我們將不可避免的多次執行?list
?的?getter
!如果你不希望有緩存,請用?method
?來替代。
4.3?偵聽器watch
????????雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。這就是為什么?Vue
?通過?watch
?選項提供了一個更通用的方法,來響應數據的變化。當需要在數據變化時執行異步或開銷較大的操作時,這個方式是最有用的。
????????當你有一些數據需要隨著其它數據變動而變動時,就可以使用Watch
來監聽他們之間的變化。一個對象,鍵是需要觀察的表達式,值是對應回調函數。值也可以是方法名,或者包含選項的對象。?Vue
?實例將會在實例化時調用?$watch()
?,遍歷?watch
?對象的每一個?property
?。
4.3.1?監聽變量的值變化
<template><view><input type="number" v-model="a" style="border: red solid 1px;" /><input type="number" v-model="b" style="border: red solid 1px;" /><view>總和:{{sum}}</view><button type="default" @click="add">求和</button></view></template><script>export default {data() {return {a:1,b:1,sum: ""}},watch: {//使用watch來響應數據的變化,第一個參數為newVal新值,第二個參數oldVal為舊值a: function(newVal, oldVal) {console.log("a--newVal: ", newVal, "a--oldVal: ",oldVal);},b: function(newVal, oldVal) {console.log("b--newVal: ", newVal, "b--oldVal: ",oldVal);}},methods: {add() {this.sum = parseInt(this.a) + parseInt(this.b)}}}</script>
????????以上示例有個問題,就是頁面剛加載時,因為沒有變化,所以不會執行。下面用immediate
來解決。
4.3.2?選項:immediate
????????在選項參數中指定?immediate: true
?將立即以表達式的當前值觸發回調:watch
方法默認就是handler
,而當immediate:true
時,就會先執行handler
方法。
<template><view><input type="number" v-model="a" style="border: red solid 1px;" /><input type="number" v-model="b" style="border: red solid 1px;" /><view>總和:{{sum}}</view><button type="default" @click="add">求和</button></view></template><script>export default {data() {return {a:1,b:1,sum: ""}},watch: {a: {handler(newVal, oldVal) {console.log("a------: ", newVal, oldVal);},immediate: true//初始化綁定時就會執行handler方法},b: {handler(newVal, oldVal) {console.log("b------: ", newVal, oldVal);},immediate: true//初始化綁定時就會執行handler方法}},methods: {add() {this.sum = parseInt(this.a) + parseInt(this.b)}}}</script>
4.3.3?選項:deep
????????為了發現對象內部值的變化,可以在選項參數中指定?deep: true
?。深度監聽一個對象整體的變化(即監聽對象所有屬性值的變化),注意監聽數組的變更不需要這么做。
<template><view><input type="number" v-model="obj.a" style="border: red solid 1px;" /><input type="number" v-model="obj.b" style="border: red solid 1px;" /><view>總和:{{sum}}</view><button type="default" @click="add">求和</button></view></template><script>export default {data() {return {obj: {a: 1,b: 1,},sum:""}},watch: {obj: {handler(newVal, oldVal) {console.log('obj-newVal:' + JSON.stringify(newVal), 'obj-oldVal:' + JSON.stringify(oldVal), );},deep: true//對象中任一屬性值發生變化,都會觸發handler方法}},methods: {add() {this.sum = parseInt(this.obj.a) + parseInt(this.obj.b)}}}</script>
4.3.4?監聽對象中單個屬性
????????如果不想監聽?obj
?中其他值,只想監聽?obj.a
?的值的變化,可以寫成字符串形式監聽。
export default {data() {return {obj: {a: 1,b: 1,}}},watch: {"obj.a": {//監聽obj對象中的單個屬性值的變化handler(newVal, oldVal) {console.log('obj-newVal:' + newVal, 'obj-oldVal:' + oldVal);}}}}
4.4?計算屬性 vs 偵聽屬性
? ?Vue
?提供了一種更通用的方式來觀察和響應?Vue
?實例上的數據變動:偵聽屬性。當你有一些數據需要隨著其它數據變動而變動時,你很容易濫用?watch
?。然而,通常更好的做法是使用計算屬性而不是命令式的?watch
?回調。
export default {data() {return {firstName: 'Foo',lastName: 'Bar',fullName: 'Foo Bar'}},watch: {firstName: function(val) {this.fullName = val + ' ' + this.lastName},lastName: function(val) {this.fullName = this.firstName + ' ' + val}}}
????????上面代碼是命令式且重復的。將它與計算屬性的版本進行比較:
export default {data() {return {firstName: 'Foo',lastName: 'Bar'}},computed: {fullName(){return this.firstName + ' ' + this.lastName}}}