React,组件在数组状态更改后不重新渲染

React, component not re-rendering after change in an array state

删除状态中的元素后,组件没有重新呈现,但状态确实发生了变化。 在组件中,您可以通过表单在数组(这是一个状态)中添加一个元素,查看数组中的所有元素,并使用按钮将其从状态中删除。因此,在删除处于状态的元素后,组件不会重新呈现。以下是组件的代码:

import React, { useEffect, useState } from 'react';
import {
  Typography,
  IconButton,
  Button,
  TextField,
  Paper,
} from '@mui/material';
import {
  CancelOutlined,
  AddBoxOutlined,
  VisibilityOutlined,
  VisibilityOffOutlined,
} from '@mui/icons-material';

export default function Test1() {
  const [subNames, setSubNames] = useState([]);
  const [subName, setSubName] = useState('');
  const [showSubForm, setShowSubForm] = useState(false);

  const onSubNameChange = (e) => {
    setSubName(e.target.value);
  };

  const onSubNameSubmit = () => {
    if (!subName) return alert('Enter name!');

    setSubNames((prev) => prev.concat({ name: subName }));
    setShowSubForm(false);
    setSubName('');
  };

  const subForm = (
    <>
      <div
        sx={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}>
        <TextField
          label='Sub Todo Name'
          onChange={onSubNameChange}
          name='subTodoName'
          value={subName}
          size='small'
          variant='outlined'
          fullWidth
        />
        <IconButton onClick={onSubNameSubmit}>
          <AddBoxOutlined color='primary' />
        </IconButton>
      </div>
      <br />
    </>
  );

  const onDelete = (position, e) => {
    let arr = subNames;

    arr.splice(position, 1);

    setSubNames(arr);
  };

  return (
    <div>
      <h1>Hello World!</h1>
      {subNames.map((item, key) => (
        <Paper
          key={key}
          sx={{
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            margin: 'auto',
            padding: 10,
            marginTop: 10,
            borderRadius: '10px',
          }}
          elevation={3}>
          <div sx={{ display: 'flex', alignItems: 'center' }}>
            <Typography variant='body1'>
              <b>Sub Todo-{key + 1}:</b>
            </Typography>
            &nbsp;
            <Typography variant='body1'>{item?.name}</Typography>
          </div>
          <IconButton onClick={(e) => onDelete(key, e)}>
            <CancelOutlined color='primary' />
          </IconButton>
        </Paper>
      ))}
      <br />
      {showSubForm && subForm}
      <div>
        {showSubForm && (
          <Button
            variant='contained'
            sx={{ float: 'right' }}
            color='primary'
            size='small'
            startIcon={<VisibilityOffOutlined />}
            onClick={() => setShowSubForm(false)}>
            Add sub todo item
          </Button>
        )}
        {!showSubForm && (
          <Button
            variant='contained'
            sx={{ float: 'right' }}
            onClick={() => setShowSubForm(true)}
            color='primary'
            size='small'
            startIcon={<VisibilityOutlined />}>
            Add sub todo item
          </Button>
        )}
      </div>
    </div>
  );
}

React 不会 re-render 因为它好像什么都没有改变,也就是说每次你给 setState 相同的 state。对于像 NumberStringBoolean 这样的原始值,很明显知道我们是否给出了不同的值。另一方面,对于 ObjectArray 等引用值,更改它们的 content 不会将它们标记为不同。应该是不同的 reference.

正如您所做的那样,您正在为 setSubNames 提供相同的内存引用。查看您的注释代码以了解您做错了什么:

let arr = subNames;      // will do a reference copy => arr == subNames
arr.splice(position, 1); // will change its content => arr == subNames
setSubNames(arr);        // at this point it's like nothing has changed

一个解决方案可能是 spread operator,将创建现有数组的副本,但在新的内存引用上,如下所示:

// changes the content of the array while keeping its memory reference
subNames.splice(position, 1); 
// the spread operator creates a copy on a new memory reference
setSubNames([...subNames]);