vue2之響應式雙向綁定,在對象或數組新增屬性頁面無響應的情況
vue2響應式雙向綁定,在對象或數組新增屬性頁面無響應
問題描述
vue2 中可以將數據與視圖進行綁定,修改 data 對象的屬性值將引起對應視圖的改變。
Vue2的數據視圖綁定是通過JS特性這一語法實現,其使用中存在數據屬性丟失的這 一 bug,主要針對 對象或數組 屬性丟失。
使用 antv 的 a-select (下拉框)組件,使用 v-mode 綁定對象 的值,但是 對象之前是沒有賦值的,是一個 空對象 ,這就導致瞭 頁面視圖不刷新 ,但是 屬性值有變化 ,這可以說是 vue2 的一個缺陷。
// 空對象 queryParam: { },
解決方法
方法一 (設置初始值)
給 綁定的對象 賦初始值 null 或者 ' '
queryParam: { approveStatus : null , },
如果是普通 輸入框 input ,這樣的方法沒什麼問題,但是 我的頁面使用 a-select 下拉框,默認有提示消息( placeholder),如果 賦初始值 為空 ,下拉框會填充空白內容 ,覆蓋之前的提示消息 ,這樣的頁面 會比較不美觀且不友好
<a-select v-model="queryParam.approveStatus" placeholder="審核狀態" :allowClear="true"> <a-select-option v-for="status of videoApproveStatus" :key="status.id"> {{ status.text }} </a-select-option> </a-select> <a-select v-model="queryParam.videotypeid" placeholder="視頻類型" :allowClear="true"> <a-select-option v-for="d in videotype" :key="d.myid"> {{ d.name }} </a-select-option> </a-select>
雖然這種方法可以解決綁定對象屬性丟失問題,但是如果給 每個屬性設置 初始值 為 null,那麼所有的下拉框都是空白 , 可以看到 下拉框 賦初始值 為 null ,頁面的效果非常不友好 ,沒有提示信息 ,所有這種方法不推薦
方法二 (創建一個新的對象,替換原對象)
這種方法可以用於需要添加多個新屬性,再把原對象與新屬性合並到新對象中
Object.assign(目標對象,原對象, 新屬性)
this.queryParam = Object.assign({}, this.queryParam, obj)
我這裡是 利用 a-select 下拉框 自帶的 下拉框 改變方法 ,@change ,該方法有兩個參數 value 和 option ,value 代表你改變的值 ,option (我也不太明白 ,反正裡面東西很多),然後 我利用 這個 @change 方法 和 Object.assign(目標對象,原對象, 新屬性), 解決瞭狀態丟失問題 ,大傢可以 參考下面的代碼 ,根據自己的實際情況進行調整 (每個人的情況都不一樣)
<a-select @change="handleChange" v-model="queryParam.approveStatus" placeholder="審核狀態" :allowClear="true"> <a-select-option v-for="status of videoApproveStatus" :key="status.id"> {{ status.text }} </a-select-option> </a-select>
handleChange(value,option) { if(option) { // 解決雙向綁定狀態丟失 this.queryParam = Object.assign({}, this.queryParam, option.context.queryParam) } else { return } },
vue2實現響應式數據
JS中的對象屬性
JS的對象有兩種屬性進行描述 分別是數據屬性和訪問器屬性。
數據屬性有四個值:
[[Configurable]]
表示是否可以刪除定義,是否可以修改屬性,默認為true[[Enumberable]]
是否可以迭代[[Writable]]
是否可以被修改[[Value]]
對象具體的值
而訪問器屬性也有四個值:
[[Configurable]]
表示是否可以刪除定義,是否可以修改屬性,默認為true[[Enumberable]]
是否可以迭代[[Get]]
獲取函數,讀取屬性值時使用[[Set]]
設置函數,寫入屬性時調用
那麼如何實現數據響應呢?
利用Object.defineProperty()進行數據劫持
實現響應式的前提是可以捕捉到到數據的更改,獲取數據同理,這就需要利用JS對象的訪問器屬性,而更改這些屬性 就要用到JS中的一個方法 Object.defineProperty() 上述的屬性在更改時,哪怕更改一個屬性,所有屬性都會變為默認值。
具體使用方法如下:
let obj = {name:'Ton', age: 19} _value = obj.name Object.defineProperty(obj, name, { get(){ return _value }, set(newValue){ _value = newValue } })
方法的參數分別是 Object.defineProperty(對象名, 屬性名, {執行器})
而get 函數在讀取該對象屬性時調用,返回的值為讀取的值; set 函數會在設置新值時調用 傳入的newValue為新值。
在Vue中 會用data一個對象包裹所有的值,因此可以用遍歷的方法給每個屬性加上該方法。
將該邏輯封裝到一個函數中:
let data = { name: 'Ton', age: 19, salary: '10k' } Object.keys(data).forEach( key => { observe(data, key, data[key]) }) //形成閉包 內部的變量不會消失 function observe(obj, key, value){ Object.defineProperty(obj, key, { get(){ return value }, set(newValue){ value = newValue } }) }
這樣data中的所有變量都會被綁定,但如果嵌套對象或數組,內部的對象不會被檢測到。(可以用vue提供的 $set和 $delate 處理或 使用內部重寫的數組方法)
與標簽聯動
與標簽聯動的方法有許多,最簡單的是使用id 來綁定,而Vue中是使用指令的方式。
過程主要分兩步:
- 獲取dom
- 將數據放上去
... <div id="app"> <div v-test="name" class="box"></div> <div v-test="age" class="box"></div> </div> ... function compile(){ let app = document.getElementById('app') //獲取所有的子節點 值為3的是text節點 1為子標簽節點 app.childNodes.forEach( node => { if(node.nodeType === 1){ //遍歷該節點的屬性 找出 v-text //node.attributes是個類數組對象, 先轉化為數組 Array.from(node.attributes).forEach( key => { //解構對象 nodeName 找到屬性 let { nodeName, nodeValue } = key //如果存在 則修改值(關鍵步驟) if(nodeName === 'v-test'){ node.innerText = data[nodeValue] } }) } }) } compile() ...
此時可以獲取節點,並賦值數據,與數據劫持聯動,最終結果如下:
let data = { name: 'Ton', age: 19} //劫持數據 Object.keys(data).forEach(key => { observe(data, key, data[key]) }) //形成閉包 內部的變量不會消失 function observe(obj, key, value) { Object.defineProperty(obj, key, { get() { return value }, set(newValue) { value = newValue compile() } }) } //獲取元素 將數據放入 function compile(){ let app = document.getElementById('app') //獲取所有的子節點 包括很多節點 3 為text 節點 1為子標簽節點 app.childNodes.forEach( node => { if(node.nodeType === 1){ //遍歷該節點的屬性 找出 v-text //node.attributes是個類數組對象, 先轉化為數組 let result = Array.from(node.attributes).filter( key => { //結構屬性的 nodeName 找到屬性 let { nodeName } = key return nodeName === 'v-test' }) if(result){ node.innerText = data[result[0].nodeValue] } } }) } compile()
每次修改也會引起DOM元素的修改,實現響應式。
v-model的實現
同v-test(v-on) 的不同,v-model要實現雙向綁定,即 input框輸入的值也需要傳回data。實現的邏輯前面是相同的,都需要獲取元素,但需要新增將input輸入框的內容,傳回。
let data = { name: 'Ton', age: 19} //劫持數據 Object.keys(data).forEach(key => { observe(data, key, data[key]) }) //形成閉包 內部的變量不會消失 function observe(obj, key, value) { Object.defineProperty(obj, key, { get() { return value }, set(newValue) { value = newValue compile() } }) } //獲取元素 將數據放入 function compile(){ let app = document.getElementById('app') //獲取所有的子節點 包括很多節點 3 為text 節點 1為子標簽節點 app.childNodes.forEach( node => { if(node.nodeType === 1){ //遍歷該節點的屬性 找出 v-text //node.attributes是個類數組對象, 先轉化為數組 let result = Array.from(node.attributes).filter( key => { //結構屬性的 nodeName 找到屬性 let { nodeName } = key return nodeName === 'v-model' }) if(result){ node.value = data[result[0].nodeValue] addEventLisener('input', e => { data[result[0].nodevalue] = e.target.value } ) } } }) } compile()
但目前的方法並不完美,需要添加一個防抖函數
let data = { name: 'Ton', age: 19} //劫持數據 Object.keys(data).forEach(key => { observe(data, key, data[key]) }) //形成閉包 內部的變量不會消失 function observe(obj, key, value) { Object.defineProperty(obj, key, { get() { return value }, set(newValue) { value = newValue compile() } }) } //獲取元素 將數據放入 function compile(){ let app = document.getElementById('app') //獲取所有的子節點 包括很多節點 3 為text 節點 1為子標簽節點 app.childNodes.forEach( node => { if(node.nodeType === 1){ //遍歷該節點的屬性 找出 v-text //node.attributes是個類數組對象, 先轉化為數組 let result = Array.from(node.attributes).filter( key => { //結構屬性的 nodeName 找到屬性 let { nodeName } = key return nodeName === 'v-model' }) if(result){ node.value = data[result[0].nodeValue] addEventLisener('input', debounce(handel, result[0].nodeValue) } } }) function debounce(fn, key, timer = 1000){ let t = null return function(){ if(t) { clearTimeout(t) } t= setTimeOut( _ => { t = null fn.call(this, key, arguments) },timer) } } function handel(key, event){ data[key] = event.target.value } } compile()
總結
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 詳細聊聊Vue.js中的MVVM
- vue MVVM雙向綁定實例詳解(數據劫持+發佈者-訂閱者模式)
- 手動實現vue2.0的雙向數據綁定原理詳解
- vue.js數據響應式原理解析
- js實現數據雙向綁定(訪問器監聽)