Vue中關於陣列與物件修改觸發頁面更新的機制與原理簡析

2022-12-09 21:00:19

Vue中關於陣列與物件修改觸發頁面更新的機制與原理簡析

相關問題

陣列

使用索引直接賦值與直接修改陣列length時,不會觸發頁面更新。

例如:

<script>
export default {
    name: "HomeView",
    data: () => ({
        list1: ["A", "B"],
    }),
    methods: {
        btnClicked() {
            this.list1[0] = "C"
            this.list1[2] = "C"
        },
    },
}
</script>

或是

<script>
export default {
    name: "HomeView",
    data: () => ({
        list1: [{ text: "123" }, { text: "456" }],
    }),
    methods: {
        btnClicked() {
            this.list1[0] = { text: "789" }
        },
    },
}
</script>

頁面並不會觸發更新。


物件

頁面初始化完成後,在方法中直接對data內宣告物件當前不存在的屬性進行賦值來為物件新增屬性時,頁面也不會響應渲染。

例如:

<script>
export default {
    name: "HomeView",
    data: () => ({
        obj1: { a: "a", b: "b" },
    }),
    methods: {
        btnClicked() {
            this.obj1.c = "c"
        },
    },
}
</script>

頁面並不會觸發更新。


原因

Vue在初始化時會將data內所有的屬性巢狀遍歷並重寫其Getter和Setter方法,藉此實現響應式屬性。

然而對於在頁面渲染完成後加入data的屬性,Vue並不會將其變為響應式。


一些深入的探究

陣列

Vue對於陣列是僅將其對應下標的物件的屬性變為響應式,而這個下標本身是無法成為響應式的

data: () => ({
        list1: [{ text: "123" }, { text: "456" }],
})

使用如上的data宣告。

方法A:

this.list1[0] = { text: "789" }

方法B:

this.list1[0].text = "789"

方法B可以被正確響應而方法A不可以。

方法A將陣列下標為0的位置替換為了一個新的物件,而因為陣列下標不是響應式的,因此沒有觸發頁面重新整理。

同時,由於陣列下標為0的位置替換為了一個新的物件,而這個新的物件並沒有被設定為響應式,那對於這個物件屬性的修改也不會觸發頁面更新。如下:

this.list1[0] = { text: "789" }
this.list1[0].text = "456"

由於新的物件的屬性並沒有被設定為響應式,那麼即使對這個物件的屬性進行修改,頁面也不會被更新。

既然下標本身無法成為響應式,不妨嘗試:

<script>
export default {
    name: "HomeView",
    data: () => ({
        list1: ["A", "B"],
    }),
    methods: {
        btnClicked() {
            this.list1[0] = "C"
        },
    },
}
</script>

通過下標修改陣列的對應值也無法觸發檢視更新。


物件

data: () => ({
        obj1: { a: { text: "a" }, b: { text: "b" } },
}),

使用如上的data宣告。

this.obj1.a = { text: "c" }

成功觸發檢視更新。

與陣列下標不同,物件的屬性在初始化是被定義為響應式的,因此直接對屬性賦值物件是能夠觸發檢視更新的。不像對陣列的對應下標賦值而不會觸發檢視更新。


解決方案

陣列

1. 內建API

如果需要向陣列加入新的成員,則可以直接使用陣列的push方法。

此外,下列陣列方法也可以自動的觸發檢視重新整理:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

2. 將陣列重新賦值,修改參照地址

為陣列新增一個字串成員"C"
this.list1 = this.list1.concat(["C"])

由於list1是data的屬性,list1的參照發生改變,就會觸發檢視更新

修改陣列的第一個值
let tempList = this.list1.concat([]) // 深拷貝,等價於一個新陣列,使用slice,JSON都可以。
tempList[0] = "666"
this.list1 = tempList

通過原陣列新建一個新陣列,修改新陣列後再將新陣列賦值給原陣列,由於原陣列作為data的屬性,其參照被修改觸發檢視更新

3. Vue.$set() 方法

使用Vue.$set可以為data物件新增一個新的響應式屬性,且觸發檢視更新

定義:

Vue.$set(物件或陣列, 物件屬性名或陣列下標, 值)

向list1物件的0索引位置賦值一個新的響應式物件,同時觸發檢視更新:

Vue.$set(this.list1, 0, { text: "789" })

如果在元件中應使用this.$set來代替:

this.$set(this.list1, 0, { text: "789" })

物件

1. 將物件重新賦值,修改參照地址

思路與陣列的類同。

使用JSON、手寫遞迴、lodash深拷貝均可,但如果物件內含方法,則不能使用JSON來完成深拷貝。

深拷貝完成後修改對應屬性後賦值給原物件即可。

2. Vue.$set() 方法

物件同樣可以使用$set() 方法修改。

定義:

Vue.$set(物件或陣列, 物件屬性名或陣列下標, 值)

將obj1物件的a屬性賦值為字串"b"並觸發檢視更新:

Vue.$set(this.obj1, a, "b")

如果在元件內,則應使用:

this.$set(this.obj1, a, "b")