Vue 2响应式将继承Array的自定义Vector类转为普通Array问题咨询
问题解答
1. 该现象是否为预期行为?背后原理是什么?
是预期行为,该问题由Vue 2的响应式实现逻辑直接导致:
- Vue 2为了实现数组的响应式更新,对所有数组类型的实例做了特殊处理:当观测数组时,会直接把数组的原型替换为Vue内部自定义的
arrayMethods对象,这个对象重写了push、pop、splice等7个数组变异方法,用来触发响应式更新。 - 你自定义的
Vector是继承自Array的子类,它的实例本质上还是数组类型,所以Vue 2在做响应式转换的时候,会直接覆盖掉它原本的Vector类原型,导致原型链上的x、y等自定义getter和类方法全部丢失,实例表现就和普通Array完全一致。
2. 兼顾响应式和保留自定义类类型的解决方案
以下是几种可行的方案:
方案1:修改Vector类的实现,改用组合替代继承
不要让Vector继承Array,改为在类内部封装数组存储值,Vue会把Vector实例当作普通对象做响应式处理,不会破坏类的原型:
export default class Vector { constructor(...args) { this.value = args } get x() { return this.value[0] } get y() { return this.value[1] } // 如果需要用到数组的方法,可以自己手动代理,比如: push(val) { return this.value.push(val) } }
使用时只要修改value属性的内容,就可以正常触发响应式更新,同时完全保留Vector类的所有自定义能力。
方案2:维护响应式触发标记,不把Vector实例交给Vue做响应式转换
如果你不想修改Vector的继承逻辑,可以把Vector实例存在非响应式的属性上,单独维护一个响应式的版本标记,每次修改Vector实例后更新标记,触发视图更新:
<script> import { square } from './vector.js'; export default { name: "HelloWorld", data() { return { renderVersion: 0, // 响应式触发标记 title: 'hello' } }, created() { // 存在非响应式属性上,不会被Vue修改原型 this.square = square }, methods: { updateVector() { // 你修改Vector实例的逻辑 this.square[0][0] = 2 // 更新标记,触发视图重新渲染 this.renderVersion += 1 } } }; </script>
模板中可以在根节点加个无用的key绑定renderVersion,就能保证每次标记更新时视图重新取最新的this.square渲染:
<template> <div id="app" :key="renderVersion"> {{ title }} ({{ square[0][0] }}, {{ square[0][1] }}) ({{ square[1].x }}, {{ square[1].y }}) </div> </template>
方案3:升级到Vue 3
Vue 3改用Proxy实现响应式,不会修改原对象的原型和结构,自定义的Array子类实例可以正常保留所有类特性,同时具备完整的响应式能力,不需要额外做兼容处理。
内容的提问来源于stack exchange,提问作者Reinhard Hameeteman
相关产品推荐
相关产品推荐

