React中自定义继承Array的Grid类多次调用构造函数并报错问题
问题:继承Array的Grid类在React组件中使用时崩溃
我试图基于Array类创建Grid数据结构,以添加自定义方法和属性,编写了如下测试代码:
import { useState } from 'react' class Grid extends Array { constructor(data) { console.log('called with', arguments); super(...data) } } function App() { const [ count ] = useState(new Grid([1,2,3])) return ( <div className="App"> {count.map(row => <p key={row.rowId}> row {row} </p>)} </div> ) } export default App
引入React组件后应用崩溃,Chrome控制台日志如下:
App.jsx:5 called with Arguments [Array(3), callee: (...), Symbol(Symbol.iterator): ƒ] App.jsx:5 called with Arguments [3, callee: (...), Symbol(Symbol.iterator): ƒ] App.jsx:5 called with Arguments [Array(3), callee: (...), Symbol(Symbol.iterator): ƒ] App.jsx:5 called with Arguments [3, callee: (...), Symbol(Symbol.iterator): ƒ] App.jsx:6 Uncaught TypeError: Found non-callable @@iterator at new Grid (App.jsx:6:5) at Grid.map (<anonymous>) at App (App.jsx:15:14) at renderWithHooks (react-dom.development.js:14985:18) at mountIndeterminateComponent (react-dom.development.js:17811:13) at beginWork (react-dom.development.js:19049:16) at HTMLUnknownElement.callCallback2 (react-dom.development.js:3945:14) at Object.invokeGuardedCallbackDev (react-dom.development.js:3994:16) at invokeGuardedCallback (react-dom.development.js:4056:31) at beginWork$1 (react-dom.development.js:23964:7)
可见Grid构造函数被多次调用,有时传入的参数是数组的最后一项,还出现了Found non-callable @@iterator的TypeError错误。请问这是React的相关行为导致的吗?
原因分析与解决方案
这不是React的问题,根源在于JavaScript中继承Array类的特殊机制:
- 数组的原生方法(比如
map、filter)默认会返回一个与原数组同类型的实例。当你调用count.map()时,map内部会用Grid构造函数来创建新实例,但它传递给构造函数的参数是新数组的长度(这里是3),而不是可迭代的数组。 - 你的Grid构造函数写了
super(...data),当data是数字3时,...data会尝试解构这个数字,但数字没有迭代器,因此抛出Found non-callable @@iterator错误。 - 构造函数多次调用是因为:初始创建Grid实例时调用一次,React渲染时触发
map,map又调用Grid构造函数创建新实例,所以出现多次日志。
方案1:重写数组方法,返回普通数组
如果不需要map等方法返回Grid实例,可以重写这些方法,让它们返回普通数组:
class Grid extends Array { constructor(data) { super(...data); } map(...args) { return Array.prototype.map.call(this, ...args); } // 按需重写filter、slice等其他数组方法 }
方案2:调整构造函数,兼容两种调用场景
如果希望map仍返回Grid实例,需要让构造函数兼容两种调用方式:一种是传入初始化数组,一种是传入长度(数组方法调用时):
class Grid extends Array { constructor(data) { if (typeof data === 'number') { // 处理数组方法传入长度的情况 super(data); } else { // 处理初始化时传入数组的情况 super(...data); } } }
方案3:使用组合模式代替继承
如果不想处理Array继承的特殊问题,更稳妥的方式是用组合模式,将数组作为Grid类的内部属性,按需暴露方法:
class Grid { constructor(data) { this._data = Array.from(data); } // 暴露map方法 map(callback) { return this._data.map(callback); } // 自定义方法示例 addRow(row) { this._data.push(row); } // 按需添加其他数组方法或属性 get length() { return this._data.length; } }
内容的提问来源于stack exchange,提问作者Juan Ignacio Dominguez
相关产品推荐
相关产品推荐

