【问题标题】:Why does my onChange event for input change multiple state values in an object array?为什么我的输入 onChange 事件会更改对象数组中的多个状态值?
【发布时间】:2020-01-26 02:45:21
【问题描述】:

我正在构建一个用户流程,以允许用户输入他们玩过的视频游戏的统计数据。我的 react 组件呈现了一个类似 excel 的表格,他们可以在其中手动输入统计数据(就像一个 excel 电子表格,其中 y 轴是一列玩家,x 轴是每个玩家的统计数据。

当我更新特定玩家的特定统计数据时,每个玩家对该特定统计数据的统计值也会更改。例如,如果我将球员 A 更新为 5 个进球,那么球队中的每个其他球员也将有 5 个进球。这不应该发生。

首先,我初始化一个 matchStats 对象:

initializeMatchStats = (numGames) => {
    const {matchInfo} = this.props;
    const gameArray = new Array(numGames).fill('');

    const matchStats = gameArray.map((game, i) => {
        let statsWithKeys = {};
        let gameInfo = {};

        matchInfo.circuitStats.map(key => {
            statsWithKeys[key.toLowerCase()] = 0
        })

            const players = new Array(matchInfo.playersAllowedInGame).fill({
                stats: statsWithKeys
            }).map(p => {
                return {
                    ...p,
                    dropdownOpen: false,
                    id: Math.random().toString(36)
                }
            })

            gameInfo = {
                players,
                index: i + 1
            }

        return gameInfo
    })
    this.setState({matchStats, numGames, numGamesModalOpen: false, uploadManually: true}, () => console.log('matchStats: ', matchStats))
}

然后,我更新用户更改的 matchStats 对象:

updateStatsManually = (val, player, game, header, e) => {

        e.preventDefault();

        const {matchStats} = this.state;
        // matchStats is an array of games in each match. A game is made up of the players (and their respective stats) in the game.
        const { matchInfo } = this.props;

        const gameToUpdate = matchStats.find(g => g.index === game.index)

        let playerToUpdate = gameToUpdate.players.find(p => p.id === player.id)

        let newStats = playerToUpdate.stats;
        newStats[header] = val;
        playerToUpdate.stats = newStats;
        gameToUpdate.players = [...gameToUpdate.players.filter(p => p.id !== player.id), playerToUpdate]

        this.setState({
            matchStats: [...matchStats.filter(g => g.index !== game.index), gameToUpdate]
        })
    }

这是反应代码块:

<Section>
    {matchStats.find(g => g.index === game).players.map((player, i) => <Row key={i}>
        {!uploadManually && <Column>
            <Input disabled style={{textAlign: 'center'}} placeholder={player.name} />
        </Column>}
        {circuitStats.map((header, i) => <Column key={`${i}-${player.id}`}>
            <Input id={`${i}-${player.id}-${header}`} value={player.stats[header.toLowerCase()]} disabled={!uploadManually} onChange={(e) => updateStatsManually(e.target.value, player, currentGame, header.toLowerCase())} />
        </Column>)}
    </Row>)}
</Section>

我希望当我更改给定玩家的一个统计数据的输入值时,它只会更改该特定玩家的统计值。但是,它会因每个玩家而改变。

我已经搞砸了一段时间了,努力想知道我做错了什么。我认为这可能与输入在 .map 函数中的呈现方式有关,但我尝试分离测试输入,结果是相同的。任何帮助表示赞赏!

【问题讨论】:

  • 你能说明这些结构是在哪里创建的吗?一定有一些别名发生了。
  • @ggorlen 我添加了初始化 matchStats 对象的代码块。希望对您有所帮助!
  • 帮助很大,谢谢。 new Array(matchInfo.playersAllowedInGame).fill({ 可能是问题所在。 .fill 只为整个 shebang 做一个对象。将其更改为 fill().map(e =&gt; {stats: statsWithKeys})... 之类的内容,并让我知道它是否有效。您可能只想将原语传递给.fill()
  • 不幸的是,这似乎不起作用。我将其更改为以下内容:` const player = new Array(matchInfo.playersAllowedInGame).fill().map(p => { return { stats: statsWithKeys, dropdownOpen: false, id: Math.random().toString(36 ) } }) `
  • 应该是fill().map(e =&gt; ({stats: statsWithKeys}));(注意额外的括号)。那么你的改变奏效了吗?问题是statsWithKeys 仍然只是一个对象,所以这是个问题。您还必须在 map 中初始化该对象。基本上,任何时候只要创建一个对象,它就会有别名,因此需要解决多个别名问题。

标签: javascript arrays reactjs object


【解决方案1】:

假设您有 5 名玩家的限制。在这种情况下,这个:

         const players = new Array(matchInfo.playersAllowedInGame).fill().map(p => {
                return {
                   stats: statsWithKeys,
                   dropdownOpen: false,
                   id: Math.random().toString(36)
                }
            })

没有像您期望的那样创建 5 个“statsWithKeys”,而是创建了 5 个对相同“statsWithKeys”的引用。

解决此问题的最佳方法是直接在对象本身上使用扩展运算符:

         const players = new Array(matchInfo.playersAllowedInGame).fill().map(p => {
                return {
                   stats: { ...statsWithKeys },
                   dropdownOpen: false,
                   id: Math.random().toString(36)
                }
            });

【讨论】:

    【解决方案2】:

    第一个问题是下面这行:

    const players = new Array(matchInfo.playersAllowedInGame).fill({
        stats: statsWithKeys
    }) // ... etc ...
    

    通过将对象传递给Array#fill,每个元素都填充了对该单个对象的引用。堆栈 sn-p 在以下最小表示中清楚地表明了这一点:

    const arr = Array(4).fill({age: 11});
    console.log(JSON.stringify(arr));
    arr[0].age = 42;
    console.log(JSON.stringify(arr));
    console.log(arr);

    将行改为

    const players = new Array(matchInfo.playersAllowedInGame).fill().map(e => ({
      stats: statsWithKeys
    })) // ... etc ...
    

    也就是说,为要引用的数组中的每个索引返回一个不同的对象。或者,由于您将其与另一个 .map 调用链接,您可以将它们合并为一个。

    这是对上述表示的修复:

    const arr = Array(4).fill().map(e => ({age: 11}));
    console.log(JSON.stringify(arr));
    arr[0].age = 42;
    console.log(JSON.stringify(arr));
    console.log(arr);

    第二个问题(与第一个问题类似)是有一个对象let statsWithKeys = {};,上面的map 函数为每个player 起别名。对于每个player,我们需要一个不同的statsWithKeys 实例。

    完全重写,将statsWithKeys 的初始化移动到map。我使用了一些虚拟数据使其可重现并消除了不必要的临时变量:

    const numGames = 3;
    const matchInfo = {
      circuitStats: ["foo", "bar"], 
      playersAllowedInGame: 4
    };
    
    const matchStats = Array(numGames).fill().map((game, i) => {
      return {
        index: i + 1,
        players: Array(matchInfo.playersAllowedInGame).fill().map(p => {
          return {
            stats: matchInfo.circuitStats.reduce((a, e) => {
              a[e.toLowerCase()] = 0;
              return a;
            }, {}),
            dropdownOpen: false,
            id: Math.random().toString(36) // this is not actually safe! use a uuid
          }
        })
      }
    });
    
    matchStats[0].players[0].stats.foo = 42; // try setting something
    console.log(matchStats);

    如上所述,另一个主要问题是Math#random is not safe for generating unique ids。如果你有很多数据(或者即使你没有并且代码运行得足够频繁),你最终会出现意想不到的行为。有关最新修复,请参阅本段中的链接。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-11
      • 1970-01-01
      • 1970-01-01
      • 2011-10-13
      • 2023-01-03
      • 1970-01-01
      • 2021-07-02
      • 2019-09-21
      相关资源
      最近更新 更多