You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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类的特殊机制:

  1. 数组的原生方法(比如map、filter)默认会返回一个与原数组同类型的实例。当你调用count.map()时,map内部会用Grid构造函数来创建新实例,但它传递给构造函数的参数是新数组的长度(这里是3),而不是可迭代的数组。
  2. 你的Grid构造函数写了super(...data),当data是数字3时,...data会尝试解构这个数字,但数字没有迭代器,因此抛出Found non-callable @@iterator错误。
  3. 构造函数多次调用是因为:初始创建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 04:05:25