关于vue更新数组项,你知道多少

plutchar 2020-11-12 10:47:07
vue 数组 知道 更新 组项


vue 中如何更新数组某一项,这是开发中经常遇到的问题

例如:data 里面有个 list 数组,数组里面有三项

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

如何将第二项的值更新为 'jerry'

不少小伙伴可能都试过 this.list[1] = 'jerry' ,但遗憾的是页面并不会更新
这种方法确实改变了 list[1] 的值,但没法触发页面更新

那么在 Vue 中,如何更新数组某一项呢?下面是总结的一些方法:

数组原生方法

Array.prototype.splice 被称为数组最强大的方法,具有删除、增加、替换功能,可以用 splice 来更新

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

为什么 splice 可以触发更新?
Vue 将被侦听的数组(这里就是list)的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

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

splice 不再是数组原生方法了,而是 Vue 重写过的方法
部分源码:

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

可以看到,splice 除了执行本身的逻辑(original.apply(this, args))之外,会把插入的值变成响应式对象(observeArray(inserted)),然后调用 ob.dep.notify() 手动触发依赖通知。
详情src/core/observer/array.js

官方 API Vue.set()

Vue.set 是官方提供的全局 API,别名 vm.$set,用来主动触发响应

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

其实 set 方法本质上还是调用的 splice 方法来触发响应,部分源码

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
}
// ...
}

set 的第一个参数为数组时,直接调用 target.splice()
详情src/core/observer/index.js

vm.$forceUpdate()

强制使 Vue 实例重新渲染,其实 this.list[1] = 'jerry' 操作,list 确实已经更改了,我们调用 vm.$forceUpdate() 可以强制渲染

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

通常你应该避免使用这个方法,而是通过数据驱动的正常方法来操作

当你无路可走的时候,可以试试这个方法,但此方法不可滥用,想想你只是想更改某个数组项,但是却可能更新了整个组件

正如官网所说的:

如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事。

深拷贝

一般粗暴的都是通过 序列化然后反序列化 回来 来实现

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

可能你还会封装自己的 cloneDeep 方法,虽然也能触发响应,但是仅仅是更新某一项就要用到深拷贝,确实有点别扭

map()

map 是数组的原生方法,用来做数组映射,类似的非变更方法(不会改变原数组)还有 sliceconcatfilter 这些,它们不会变更原始数组,而总是返回一个新数组
那么在 Vue 中我们直接替换数组也是可以实现更新的

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

你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,Vue 做的够多。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。还记得在模板中使用 v-for 必须要提供 key 吗,Vue 根据这个 key 值能够更高效的找出差异,精准定位并更新。

实际上,这种基于源数据生成新数据(同时不影响外部)的方式符合函数式编程的思想,如果你用过 redux,那么在写 reducer 时会经常用到 mapfilter 这些

数组项为对象的情况

在开发中可能会遇到 this.list[1].name = 'jerry' 这种情况,如果数组项是对象,那么是可以通过下标直接更新这个对象的属性
其实是 Vue 在初始化数组的时候做了处理,对于数组项是非对象会直接返回,不做操作,如果是对象,那么会用 Observer 类初始化它,给对象属性加上 gettersetter监听器
部分源码:

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 类初始化时,如果是数组,一般情况下会调用 protoAugment()observeArray()protoAugment 会将 value(数组) 的 __proto__ 指向 arrayMethods,这里着重看 observeArray,它会在每个数组项调用 observe()observe 如下:

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
}

可以看到,如果数组项不是对象,会直接返回;数组项为对象,会对该对象继续进行 Observer 初始化,进而调用 walk(),对每个属性调用 defineReactive()
defineReactive 会通过Object.defineProperty给属性加上 gettersetter监听器,所以给数组项重新赋值就会触发响应了
详情src/core/observer/index.js

关于为什么 Vue 没有将 arr[index] = val 变成响应式,网上有很多讨论,作者也有回答,大体来说,就是 性能代价和获得的用户体验不成正比
arr[index] = val 虽然不是响应式,但也有提供的官方 API 来操作,作为一个框架,Vue 已经做的够多了。当然 Vue3Object.defineProperty 换成了 Proxy,那么这个问题也就不复存在了。

版权声明
本文为[plutchar]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000038142224

  1. [front end -- JavaScript] knowledge point (IV) -- memory leakage in the project (I)
  2. This mechanism in JS
  3. Vue 3.0 source code learning 1 --- rendering process of components
  4. Learning the realization of canvas and simple drawing
  5. gin里获取http请求过来的参数
  6. vue3的新特性
  7. Get the parameters from HTTP request in gin
  8. New features of vue3
  9. vue-cli 引入腾讯地图(最新 api,rocketmq原理面试
  10. Vue 学习笔记(3,免费Java高级工程师学习资源
  11. Vue 学习笔记(2,Java编程视频教程
  12. Vue cli introduces Tencent maps (the latest API, rocketmq)
  13. Vue learning notes (3, free Java senior engineer learning resources)
  14. Vue learning notes (2, Java programming video tutorial)
  15. 【Vue】—props属性
  16. 【Vue】—创建组件
  17. [Vue] - props attribute
  18. [Vue] - create component
  19. 浅谈vue响应式原理及发布订阅模式和观察者模式
  20. On Vue responsive principle, publish subscribe mode and observer mode
  21. 浅谈vue响应式原理及发布订阅模式和观察者模式
  22. On Vue responsive principle, publish subscribe mode and observer mode
  23. Xiaobai can understand it. It only takes 4 steps to solve the problem of Vue keep alive cache component
  24. Publish, subscribe and observer of design patterns
  25. Summary of common content added in ES6 + (II)
  26. No.8 Vue element admin learning (III) vuex learning and login method analysis
  27. Write a mini webpack project construction tool
  28. Shopping cart (front-end static page preparation)
  29. Introduction to the fluent platform
  30. Webpack5 cache
  31. The difference between drop-down box select option and datalist
  32. CSS review (III)
  33. Node.js学习笔记【七】
  34. Node.js learning notes [VII]
  35. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  36. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  37. 【JQuery框架,Java编程教程视频下载
  38. [jQuery framework, Java programming tutorial video download
  39. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  40. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  41. 【Vue,阿里P8大佬亲自教你
  42. 【Vue基础知识总结 5,字节跳动算法工程师面试经验
  43. [Vue, Ali P8 teaches you personally
  44. [Vue basic knowledge summary 5. Interview experience of byte beating Algorithm Engineer
  45. 【问题记录】- 谷歌浏览器 Html生成PDF
  46. [problem record] - PDF generated by Google browser HTML
  47. 【问题记录】- 谷歌浏览器 Html生成PDF
  48. [problem record] - PDF generated by Google browser HTML
  49. 【JavaScript】查漏补缺 —数组中reduce()方法
  50. [JavaScript] leak checking and defect filling - reduce() method in array
  51. 【重识 HTML (3),350道Java面试真题分享
  52. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  53. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  54. [re recognize HTML (3) and share 350 real Java interview questions
  55. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  56. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  57. 【重识 HTML ,nginx面试题阿里
  58. 【重识 HTML (4),ELK原来这么简单
  59. [re recognize HTML, nginx interview questions]
  60. [re recognize HTML (4). Elk is so simple