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

React组件内增删功能故障排查及嵌套组件实现咨询

问题描述

我是JavaScript和React新手,正在开发个人项目,需要实现用户可添加、删除自定义玩家配置的功能,计划通过按钮按需增删组件。目前添加功能正常,但删除功能存在bug:给玩家组件添加stat组件后,删除该玩家组件时,对应的stat组件会转移到下一个玩家组件中。希望删除玩家时同时删除其关联的所有stat组件,且要在stat组件内再添加组件,需确保组件嵌套实现高效可行。

当前使用uuid追踪操作,不确定是否需在删除函数中调用它。暂未做前端设计,计划使用Material-UI,打算先实现功能再做设计。

原代码

import React, { Component, useState } from 'react';

import Button from '@material-ui/core/Button'
import ButtonGroup from '@material-ui/core/ButtonGroup';
import Container from '@material-ui/core/Container'
import Grid from '@material-ui/core/Grid'
import DeleteIcon from '@material-ui/icons/Delete'


function Stats({ allStats, editedStats, statPlayerKey, statKey }) {
  
  const removeStats = () => {
    const filteredStats = allStats.filter((stat) => stat.statKey !== statKey);
    editedStats(filteredStats);
  }

  return (
    <Grid containter>
      <Button onClick={removeStats}>
        <DeleteIcon />
      </Button>
      <Grid item>
        {statPlayerKey} {statKey}
      </Grid>
    </Grid>
  )
}

function Player({ allPlayers, editedPlayers, playerKey, name }) {

  const [stats, setStats] = useState([])

  const handleRemove = () => {
    const filteredPlayers = allPlayers.filter((player) => player.playerKey !== playerKey);
    editedPlayers(filteredPlayers);
  };

  const statsList = stats.map(({statKey, statPlayerKey}) => (
    <Container>
      <Stats 
        allStats={stats}
        statKey={statKey}
        statPlayerKey={statPlayerKey}
        editedStats={setStats}
      />
    </Container>
  ))

  const addStat = () => {
    setStats([...stats, {statPlayerKey: playerKey, statKey: uuid()}])
  }

  return (
    <Grid container>
      <Button onClick={handleRemove}>
        <DeleteIcon />
      </Button>
      <Grid item>
        {playerKey} {name}
      </Grid>
      <Grid item>
        {statsList}
      </Grid>
      <Button onClick={() => addStat()}>
        Add Stat
      </Button>
    </Grid>
  );
};


function TestApp() {
  const [players, setPlayers] = useState([
    {
      playerKey: uuid(),
      name: "Mario"
    },
    {
      playerKey: uuid(),
      name: "Luigi"
    },
    {
      playerKey: uuid(),
      name: "Toad"
    }
  ]);

  const addPlayer = () => {
    setPlayers([...players, {
      playerKey: uuid(),
      name: 'Bowser'
    }])
  }

  const playersList = players.map(({ playerKey, name }) => (
    <Container>
      <Player
        allPlayers={players}
        editedPlayers={setPlayers}
        playerKey={playerKey}
        name={name}
      />
    </Container>
  ));

  return (
    <div className="App">
      <h1>Team Members ({players.length})</h1>
      <Container>{playersList}</Container>
      <Button onClick={addPlayer}>ADD</Button>
    </div>
  );
}


export default TestApp;

问题分析
  1. 状态分散导致数据不同步:每个Player组件单独维护自己的stats状态,删除玩家时,React因列表渲染未加唯一key会复用组件,导致被删玩家的stats被带到下一个玩家组件中。
  2. 数据结构不合理:玩家和stat数据分离存储,删除玩家时无法直接关联清理对应的stat,且上层组件无法统一管理所有stat数据。
解决方案

核心优化点

  • 统一状态管理:将所有玩家及关联的stat数据移至顶层TestApp组件维护,确保数据流向清晰。
  • 添加唯一key:给列表渲染的组件添加唯一标识,避免React错误复用组件。
  • 简化操作逻辑:通过顶层回调函数处理增删操作,确保数据修改的一致性。

修改后的完整代码

import React, { useState } from 'react';
import { v4 as uuidv4 } from 'uuid'; // 需先安装uuid包:npm install uuid

import Button from '@material-ui/core/Button';
import Container from '@material-ui/core/Container';
import Grid from '@material-ui/core/Grid';
import DeleteIcon from '@material-ui/icons/Delete';

function Stats({ stat, playerKey, onDeleteStat }) {
  const removeStats = () => {
    onDeleteStat(playerKey, stat.statKey);
  };

  return (
    <Grid container alignItems="center" spacing={1}>
      <Grid item>
        <Button onClick={removeStats}>
          <DeleteIcon />
        </Button>
      </Grid>
      <Grid item>
        {playerKey} {stat.statKey}
      </Grid>
    </Grid>
  );
}

function Player({ player, onDeletePlayer, onAddStat, onDeleteStat }) {
  const statsList = player.stats.map((stat) => (
    <Container key={stat.statKey}>
      <Stats 
        stat={stat}
        playerKey={player.playerKey}
        onDeleteStat={onDeleteStat}
      />
    </Container>
  ));

  return (
    <Grid container alignItems="center" spacing={2} style={{ margin: '10px 0' }}>
      <Grid item>
        <Button onClick={() => onDeletePlayer(player.playerKey)}>
          <DeleteIcon />
        </Button>
      </Grid>
      <Grid item>
        {player.playerKey} {player.name}
      </Grid>
      <Grid item>
        {statsList}
      </Grid>
      <Grid item>
        <Button onClick={() => onAddStat(player.playerKey)}>
          Add Stat
        </Button>
      </Grid>
    </Grid>
  );
};

function TestApp() {
  const [players, setPlayers] = useState([
    {
      playerKey: uuidv4(),
      name: "Mario",
      stats: []
    },
    {
      playerKey: uuidv4(),
      name: "Luigi",
      stats: []
    },
    {
      playerKey: uuidv4(),
      name: "Toad",
      stats: []
    }
  ]);

  const addPlayer = () => {
    setPlayers([...players, {
      playerKey: uuidv4(),
      name: 'Bowser',
      stats: []
    }]);
  };

  const deletePlayer = (playerKey) => {
    // 删除玩家时直接过滤,其关联的stats会随玩家对象一起被移除
    setPlayers(players.filter(player => player.playerKey !== playerKey));
  };

  const addStat = (playerKey) => {
    setPlayers(players.map(player => {
      if (player.playerKey === playerKey) {
        return {
          ...player,
          stats: [...player.stats, { statKey: uuidv4() }]
        };
      }
      return player;
    }));
  };

  const deleteStat = (playerKey, statKey) => {
    setPlayers(players.map(player => {
      if (player.playerKey === playerKey) {
        return {
          ...player,
          stats: player.stats.filter(stat => stat.statKey !== statKey)
        };
      }
      return player;
    }));
  };

  const playersList = players.map((player) => (
    <Container key={player.playerKey}>
      <Player
        player={player}
        onDeletePlayer={deletePlayer}
        onAddStat={addStat}
        onDeleteStat={deleteStat}
      />
    </Container>
  ));

  return (
    <div className="App">
      <h1>Team Members ({players.length})</h1>
      <Container>{playersList}</Container>
      <Button onClick={addPlayer} style={{ marginTop: '10px' }}>ADD</Button>
    </div>
  );
}

export default TestApp;

关键说明

  • 统一状态管理:所有玩家和stat数据集中在顶层,删除玩家时直接移除对应对象,关联的stat会被自动清理,不会出现stat转移问题。
  • uuid的正确用法:仅在创建新玩家或stat时生成uuid,删除操作只需用已生成的uuid匹配目标对象即可,无需重新调用uuid。
  • 组件嵌套扩展性:当前结构支持在Stats组件内继续添加子组件,只需在stat对象中扩展数据,并传递对应操作回调,数据流向清晰,维护性强。
  • 唯一key的作用:给每个列表项添加唯一key,确保React能准确识别组件,避免因复用导致的状态混乱。

内容的提问来源于stack exchange,提问作者adev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 04:40:28