vue中使用對象數組的最佳實踐


前言:

    在平常的開發中,經常會在vue中用到對象數組,如渲染一個小區的列表,數據結構可能如下所示:

CommunityList: [
    {
        _id: '',
        community_code: '',
        community_name: '',
        ...
    },
    ...
 ]

    當需要實現選擇某幾個小區加入收藏列表的時候,頁面上往往需要增加el-checkbox來勾選,在數據結構上需要增加一個類似relation屬性來標志該小區是否被選擇。

    所以在從接口拿到數據的時候需要在數據上增加relation屬性,處理方式可能如下所示:

/** result是從接口獲取的數據 **/
if (result && result.retCode === 0) {
  this.communityList = result.list.map(item => {
    item.relation = false;
    return item;
  })
}

  

    當你碰巧在處理數據之前先將result.list賦值給了另一個實例屬性,如下所示:

/** result是從接口獲取的數據 **/
if (result && result.retCode === 0) {
  this.communityListCache = result.list; // 新增的代碼
  this.communityList = result.list.map(item => {
    item.relation = false;
    return item;
  })
}

  

    那么此時el-checkbox的表現可能會不太正常,如下所示:

    點擊時狀態不會立馬改變,而是等鼠標移開了才改變

              

 

    下面兩個圖均是在el-checkbox為灰色狀態下點擊的結果,一個為true,另一個卻是false

 

             

             

 

分析:

      經過打斷點分析發現某些屬性並不是像其它屬性一樣的表現

                

 

      如這里的relation屬性(el-checkbox綁定的就是該屬性),不像下面的_id屬性所表現的一致,_id屬性所表現的就是正常的被vue監聽的狀態,很明顯relation並沒有被vue監聽,問題就出現在新增的代碼那里。

      通過分析vue的源代碼可知,實例屬性被賦值的時候會使用Object.defineProperties方法將其轉化為可被監聽的屬性,如果賦值了一個數組,也會將數組轉化為可被監聽的對象,且會深入內部監聽如下:

      截取自vue源代碼中的部分片段:

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value) // 當為數組時,轉化數組
    } else {
      this.walk(value)
    }
  }

...

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i]) // 逐級轉化
    }
  }
}

    又因為js中引用類型只是指向實際數據的類似指針的結構,此時this.communityListCache和result.list在內存中實際就是指向同一個地址,當vue轉化this.communityListCache的時候實際就是轉化了result.list

this.communityListCache = result.list; // 新增的代碼

    vue會在result.list中新增__ob__這個屬性來標志已經被轉化過了,如下圖所示:

          

 

    由於relation是在之后才新增的屬性,而在vue轉化的時候,如果對象有__ob__屬性,則表示已經被轉化過了,不會再次去處理,如下:

    截取自vue源代碼中的部分片段:

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 判斷是否本來就存在__ob__屬性,如果存在就直接賦值,不做處理
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

  這樣后面新增的relation屬性實際就是沒有被vue所監聽的屬性,自然就不會響應變化。

 

解決辦法:

      1、在完全處理好原始數據后再賦值給其它的實例屬性,如下:

/** result是從接口獲取的數據 **/
if (result && result.retCode === 0) {
  // this.communityCache = result.list; // 原始代碼
  this.communityList = result.list.map(item => {
    item.relation = false;
    return item;
  });
  this.communityCache = result.list; // 新增的代碼
}

  

    2、由於接口返回的數據一般沒有包涵特殊類型,所以在賦值之前進行簡單的深拷貝即可,這樣的話,這兩個變量就不是指向同一個對象,也就不會相互影響:

/** result是從接口獲取的數據 **/
if (result && result.retCode === 0) {
  // this.communityCache = result.list; // 原始代碼
  this.communityCache = JSON.parse(JSON.stringify(result.list)); // 新增代碼
  this.communityList = result.list.map(item => {
    item.relation = false;
    return item;
  });
  this.communityCache = result.list; // 新增的代碼
}

  

  注:個人總結,如有不對,還望指正,謝謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM