编程知识 cdmana.com

How much do you know about Vue updating array items

vue How to update an item in an array , This is a common problem in development

for example :data There's a list Array , There are three items in the array

export default {
  data() {
    return {
      list: [
        'foo',
        'bar',
        'baz'
      ]
    }
  }
}

How to update the value of the second item to 'jerry'

Many of my friends may have tried this.list[1] = 'jerry' , Unfortunately, the page doesn't update
This approach has really changed list[1] Value , But there's no way to trigger page updates

So in Vue in , How to update an item in an array ? Here are some ways to sum up :

Array native methods

Array.prototype.splice It's called the most powerful method of arrays , With delete 、 increase 、 Replacement function , It can be used splice To update

this.list.splice(1, 1, 'jerry')

Why? splice Can trigger updates ?
Vue The array that will be listened to ( Here is the list) The change method of the package , So they will also trigger view updates . These wrapped methods include :

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

splice It's no longer an array native method , It is Vue The rewritten method
Part of the source code :

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

You can see ,splice In addition to executing its own logic (original.apply(this, args)) outside , It turns the inserted value into a responsive object (observeArray(inserted)), And then call ob.dep.notify() Manually trigger dependency notification .
details src/core/observer/array.js

official API Vue.set()

Vue.set It's an official overall picture API, Alias vm.$set, Used to trigger the response actively

Vue.set(this.list, 1, 'jerry')
//  perhaps 
this.$set(this.list, 1, 'jerry')

Actually set Method is still called in essence splice Method to trigger the response , Part of the source code

function set (target: Array<any> | Object, key: any, val: any): any {
  // ...
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  // ...
}

When set When the first parameter of is an array , Call directly target.splice()
details src/core/observer/index.js

vm.$forceUpdate()

To compel Vue Instance re rendering , Actually this.list[1] = 'jerry' operation ,list It has indeed changed , We call vm.$forceUpdate() You can force rendering

this.list[1] = 'jerry'
this.$forceUpdate()

Usually you should avoid using this method , It's done in a normal way, data driven

When you have no way to go , Try this method , But this method should not be abused , Imagine that you just want to change an array item , But it may have updated the entire component

As the official website said :

If you find yourself in need of Vue Do a forced update in ,99.9% The situation of , You did something wrong somewhere .

Deep copy

Generally, the roughest way is through Serialize and then deserialize Come back To achieve

this.list[1] = 'jerry'
this.list = JSON.parse(JSON.stringify(this.list))

Maybe you'll encapsulate your own cloneDeep Method , Although it can trigger a response , But just updating an item requires deep copy , It's a bit awkward

map()

map Is the native method of an array , Used for array mapping , A similar non change approach ( It doesn't change the original array ) also sliceconcatfilter these , They don't change the original array , And always returns a new array
So in Vue We can also update the array by replacing it directly

this.list = this.list.map((item, index) => {
  if (index === 1) {
    return 'jerry'
  }
  return item
})

You may think that this will lead to Vue Discard the existing DOM And re render the entire list . Fortunately, ,Vue Do enough .Vue In order to make DOM Elements are reused to the maximum extent, and some intelligent heuristics are implemented , So it is very efficient to replace the original array with an array containing the same elements . Remember to use... In the template v-for You have to provide key Do you ,Vue According to this key Values can be used to find differences more efficiently , Pinpoint and update .

actually , This is based on source data to generate new data ( At the same time, it doesn't affect the outside ) The way to meet the idea of functional programming , If you used redux, So I'm writing reducer You will often use mapfilter these

When an array item is an object

You may encounter this.list[1].name = 'jerry' This situation , If the array item is an object , Then you can directly update the properties of this object by subscript
It's actually Vue We did some processing when initializing the array , If the array item is not an object, it will directly return , No operation , If it's an object , Then I can use Observer Class initializes it , Add... To the object properties gettersetter Monitor
Part of the source code :

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)
    }
  }
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

Observer Class initialization , If it's an array , In general, it will call protoAugment()observeArray().protoAugment Will value( Array ) Of __proto__ Point to arrayMethods, Here we focus on observeArray, It calls observe(),observe as follows :

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 = 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
}

You can see , If the array item is not an object , Will go straight back to ; Array items are objects , Will continue with the object Observer initialization , And then call walk(), Call... For each property defineReactive(),
defineReactive Will pass Object.defineProperty Add... To attributes gettersetter Monitor , So reassigning an array item triggers the response
details src/core/observer/index.js

About why Vue No will arr[index] = val Become responsive , There's a lot of discussion online , The author also answers , In general , Namely The cost of performance is not directly proportional to the user experience .
arr[index] = val Although not responsive , But there are also official offers API To operate , As a framework ,Vue Enough has been done . Of course Vue3 take Object.defineProperty Instead of Proxy, Then the problem is gone .

版权声明
本文为[Plutchar.]所创,转载请带上原文链接,感谢

Scroll to Top