最近使用vue開發時,在一個函數中使用for循環,改變了頁面中的數組,在函數中查看是修改成功的,但是頁面中沒有動態刷新。
在Vue的官方文檔有提到這樣一個注意事項:
數組變更檢測注意事項:
由於 JavaScript 的限制,Vue 不能檢測以下數組的變動:
- 當你利用索引直接設置一個數組項時,例如:
vm.items[indexOfItem] = newValue
- 當你修改數組的長度時,例如:
vm.items.length = newLength
舉個例子:
var vm = new Vue({ data: { items: ['a', 'b', 'c'] } }) vm.items[1] = 'x' // 不是響應性的 vm.items.length = 2 // 不是響應性的
也就是說,直接設置數組的某一項的值,雖然改變了數組的值,但視圖上顯示的仍為數組之前的值,數據的響應失效了。出現這種現象的根本原因是什么呢?
首先我們先來了解vue數據響應的原理。官方文檔的解釋:
當你把一個普通的 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象所有的屬性,並使用 Object.defineProperty 把這些屬性全部轉為 getter/setter。
也就是說當改變data中屬性的值時會觸發其相應setter的調用,從而實現響應的操作。但getter和setter是有局限性的。我們先來看下面的這個例子:
var person = {}; Object.defineProperties( person, { age: { defaultValue: 11, get: function () { return this.defaultValue; }, set: function (val) { this.defaultValue = val; console.log("觸發了set") } } }); // 修改屬性的值時能夠觸發set person.age = 12 // 觸發了set ->觸發了set ->12 person.age ->12 // 將屬性的值設置為一個數組,當通過索引值修改數組的某一項或使用數組的某些方法修改數組時不能觸發set person.age = [2,3,4] // 觸發了set ->觸發了set ->(3) [2, 3, 4] person.age[2] = 5 // 未觸發set ->5 person.age ->(3) [2, 3, 5] person.age.push(5) // 未觸發set ->4 person.age ->(4) [2, 3, 4, 5] // 將屬性的值設置為一個對象,當修改對象中某屬性的值時無法觸發set person.age = { first: 1 } ->觸發了set ->{first: 1} person.age.first = 2 // 未觸發set ->2
通過上述例子可以觀察得出:
當該屬性的值為一個數組時,通過索引修改數組某一項的值或使用數組的某些方法修改數組並不能觸發set;當屬性的值為一對象時,直接修改對象中屬性的值時也無法觸發set。
為了解決當你利用索引直接設置一個數組項問題,以下兩種方式都可以實現和 vm.items[indexOfItem] = newValue
相同的效果,同時也將在響應式系統內觸發狀態更新:
// Vue.set Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set
實例方法,該方法是全局方法 Vue.set
的一個別名:
vm.$set(vm.items, indexOfItem, newValue)
為了解決當你修改數組的長度問題,你可以使用 splice
:
vm.items.splice(newLength)
對象變更檢測注意事項:
還是由於 JavaScript 的限制,Vue 不能檢測對象屬性的添加或刪除:
var vm = new Vue({ data: { a: 1 } }) // `vm.a` 現在是響應式的 vm.b = 2 // `vm.b` 不是響應式的
對於已經創建的實例,Vue 不允許動態添加根級別的響應式屬性。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套對象添加響應式屬性。例如,對於:
var vm = new Vue({ data: { userProfile: { name: 'Anika' } } })
當修改對象的屬性或為對象添加屬性時,應該使用以下方法:
Vue.set(vm.userProfile, 'age', 27)
或者
vm.$set(vm.userProfile, 'age', 27)
有時你可能需要為已有對象賦值多個新屬性,比如使用 Object.assign()
或 _.extend()
。在這種情況下,你應該用兩個對象的屬性創建一個新的對象。所以,如果你想添加新的響應式屬性,不要像這樣:
Object.assign(vm.userProfile, { age: 27, favoriteColor: 'Vue Green' })
你應該這樣做:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
由於數據響應原理機制, Vue 不允許動態添加根級響應式屬性,所以你必須在初始化實例前聲明所有可能用到的根級響應式屬性,且為這些屬性都設一個初值,哪怕只是一個空值。
回歸正題,我項目中遇到的這個問題,解決方法:
1. 運用this.$forceUpdate()強制刷新。
2. 使用vm.$set(vm.items, indexOfItem, newValue)
eg. vm.$set(vm.dataList[i], picUrl, 'data:image/jpg;base64,' + response.data)
export default { data() { return { dataList:[], }; }, methods: { getData() { var _this = this; var dataList = []; dataList = response.data.data; for(var i=0;i<dataList.length;i++){ _this.downloadBase64(dataList[i].fielID, i, dataList); } }, downloadBase64(fielID, i, dataList) { var vm = this; $.ajax({ url: AppInter.downloadBase64, asysc: true, data: { fileName: '1.jpg', fileId: fielID }, cache: !0, timeout: 2e4, dataType: "json", type: "POST", xhrFields: { withCredentials: !0 }, crossDomain: !0, contentType: "application/x-www-form-urlencoded;charset=UTF-8", beforeSend: function () { vm.loading = weui.loading(); }, success: function (response) { if (response.responseCode == '0') { dataList[i].picUrl = 'data:image/jpg;base64,' + response.data; vm.dataList = dataList; vm.$forceUpdate(); } else { weui.alert("請求錯誤,請稍候再試", function () {}, { title: '溫馨提示' }); } }, error: function (xhr, msg, err) { weui.alert("請求失敗,請稍候再試", function () {}, { title: '溫馨提示' }); }, complete: function () { if (vm.loading.hide) { vm.loading.hide(); } } }) } }, created() { }, mounted() { } };