React Hook:useState的set函数不触发重新渲染

React Hook: The set function of useState does not trigger re-render

我目前正在处理一个简化的 Trello 克隆项目。我定义了一个boardListuseState,并实现了两个函数create()remove(),可以添加或删除一个板、列表或卡片。一切正常,除非删除列表。

当我单击删除列表的按钮时,组件不会立即重新呈现。删除板和卡可以立即更改。就列表而言,它似乎没有重新呈现,尽管 boardList 已被修改为删除列表。当我转到主页并返回时应用更改。我很好奇为什么 setBoardList() 只在这种特定情况下不触发渲染。我的函数总是提供一个新对象 newBoardList,所以它可能不是关于返回 react 无法识别的同一个对象。

下面是我的App.jsBoard.jsHome路由显示看板,用户可以点击其中任意一个进入BoardPage路由,修改看板内的列表和卡片。 Board.js 包括与问题相关的 deleteList()

你可以在这里看到完整的项目:https://codesandbox.io/s/upbeat-lederberg-l3e3e

App.js

import React from 'react';
import { HashRouter, Route } from 'react-router-dom';
import { createGlobalStyle } from "styled-components";
import Home from './routes/Home';
import BoardPage from './routes/BoardPage';

const GlobalStyle = createGlobalStyle`
    body{
        padding: 0;
        margin: 0;
        box-sizing: border-box;
        font-family: 'Montserrat';
    }
`;

export const EMPTY =  '-';

function App() {
    const [boardList, setBoardList] = React.useState([]);
    const create = (boardKey, listKey, text) => {
      if(boardKey===EMPTY){ //createBoard
        const check = boardList.filter(board => board.boardName === text);
        if(!check.length && text.length){
            const newBoardList = boardList.concat([{boardKey: text.concat(Date.now()), boardName: text, listList: []}]);
            setBoardList(newBoardList);
        } 
      }
      else if(listKey===EMPTY){ //createList
        const newBoardList = [...boardList];
        newBoardList.forEach(board => {
          if(board.boardKey===boardKey){
            const check = board.listList.filter(list => list.listName === text);
            if(!check.length && text.length){
              board.listList.push({listKey: text.concat(Date.now()), listName: text, cardList: []});
            }
          }
        });
        setBoardList(newBoardList);
      }
      else{ //createCard
        const newBoardList = [...boardList];
        newBoardList.forEach(board => {
          if(board.boardKey===boardKey){
            board.listList.forEach(list => {
              if(list.listKey===listKey){
                const check = list.cardList.filter(card => card.content === text);
                if(!check.length && text.length){
                  list.cardList.push({cardKey: text.concat(Date.now()), content: text});
                }
              }
            });
          }
        });
        setBoardList(newBoardList);
      } 
    };


    const remove = (boardKey, listKey, cardKey) => {
      if(boardKey===EMPTY){
        return;
      }
      else{
        if(listKey===EMPTY){ //removeBoard
          const newBoardList = boardList.filter(board => board.boardKey !== boardKey);
          setBoardList(newBoardList);   
        }
        else if(cardKey===EMPTY){ //removeList
          const newBoardList = [...boardList];
          newBoardList.forEach(board => {
            if(board.boardKey===boardKey){
              board.listList = board.listList.filter(list => list.listKey !== listKey);
            }
          });
          setBoardList(newBoardList);
        }
        else{ //removeCard
          const newBoardList = [...boardList];
          newBoardList.forEach(board => {
            if(board.boardKey===boardKey){
              board.listList.forEach(list => {
                if(list.listKey===listKey){
                  list.cardList = list.cardList.filter(card => card.cardKey !== cardKey);
                }
              });
            }
          });
          setBoardList(newBoardList);
        }
      }
    };
    return (
        <>
            <GlobalStyle />
            <HashRouter>
              <Route path="/" exact={true} render={props => <Home {...props} boardList={boardList} create={create} remove={remove}/>} />
              <Route path="/board/:boardName" render={props => <BoardPage {...props} create={create} remove={remove} />} />
            </HashRouter>
        </>
    );
}

export default App;

Board.js

import React, { useState } from 'react';
import styled from 'styled-components';
import List from './List';
import {EMPTY} from '../App';

export default function Board({boardKey, boardName, listList, create, remove}) {
    const [text, setText] = useState("");
    const createNewList = text => {
        create(boardKey, EMPTY, text);
    };
    const deleteList = (key) => {
        remove(boardKey, key, EMPTY);
    };
    const onChange = e =>{
        setText(e.target.value);
    };
    const onSubmit = e => {
        e.preventDefault();
        createNewList(text);
        setText("");
    };
    return(
        <BoardContainer>
            <h4>{boardName}</h4>
            {listList.map(list => (
                <span key={list.listKey}>
                    <List boardKey={boardKey} listKey={list.listKey} listName={list.listName} cardList={list.cardList} create={create} remove={remove} />
                    <button onClick={()=>deleteList(list.listKey)}>DelL</button>
                </span>
            ))}
            <ListAdder>
                <input type="text" value={text} onChange={onChange} />
                <button onClick={onSubmit}>AddL</button>
            </ListAdder>
        </BoardContainer>
    );

}

const BoardContainer = styled.div`
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-items: flex-start;
`;

const ListAdder = styled.div`
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    border: 1px solid gray;
`;

问题是您将状态保存在两个不同的地方,使其不一致。您将 board.listList 作为 Home 组件中的位置状态传递,并且此引用不会跟踪 AppboardList 的更改。

我在这里做了一个例子,它似乎按预期工作: https://codesandbox.io/s/sharp-mclaren-pv2s2?fontsize=14&hidenavigation=1&theme=dark

在我链接的代码中,我们不再传递位置状态的列表,而是将 boardList 传递给 BoardPage 并根据 [= 从那里提取正确的板16=]。这使我们能够始终访问当前状态。

当然有多种方法可以做到这一点,您可能更喜欢与我的示例不同的风格。只需确保传递实际状态而不是创建对它的单独引用。