React Hook:useState的set函数不触发重新渲染
React Hook: The set function of useState does not trigger re-render
我目前正在处理一个简化的 Trello 克隆项目。我定义了一个boardList
和useState
,并实现了两个函数create()
和remove()
,可以添加或删除一个板、列表或卡片。一切正常,除非删除列表。
当我单击删除列表的按钮时,组件不会立即重新呈现。删除板和卡可以立即更改。就列表而言,它似乎没有重新呈现,尽管 boardList
已被修改为删除列表。当我转到主页并返回时应用更改。我很好奇为什么 setBoardList()
只在这种特定情况下不触发渲染。我的函数总是提供一个新对象 newBoardList
,所以它可能不是关于返回 react 无法识别的同一个对象。
下面是我的App.js
和Board.js
。 Home
路由显示看板,用户可以点击其中任意一个进入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
组件中的位置状态传递,并且此引用不会跟踪 App
中 boardList
的更改。
我在这里做了一个例子,它似乎按预期工作:
https://codesandbox.io/s/sharp-mclaren-pv2s2?fontsize=14&hidenavigation=1&theme=dark
在我链接的代码中,我们不再传递位置状态的列表,而是将 boardList
传递给 BoardPage
并根据 [= 从那里提取正确的板16=]。这使我们能够始终访问当前状态。
当然有多种方法可以做到这一点,您可能更喜欢与我的示例不同的风格。只需确保传递实际状态而不是创建对它的单独引用。
我目前正在处理一个简化的 Trello 克隆项目。我定义了一个boardList
和useState
,并实现了两个函数create()
和remove()
,可以添加或删除一个板、列表或卡片。一切正常,除非删除列表。
当我单击删除列表的按钮时,组件不会立即重新呈现。删除板和卡可以立即更改。就列表而言,它似乎没有重新呈现,尽管 boardList
已被修改为删除列表。当我转到主页并返回时应用更改。我很好奇为什么 setBoardList()
只在这种特定情况下不触发渲染。我的函数总是提供一个新对象 newBoardList
,所以它可能不是关于返回 react 无法识别的同一个对象。
下面是我的App.js
和Board.js
。 Home
路由显示看板,用户可以点击其中任意一个进入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
组件中的位置状态传递,并且此引用不会跟踪 App
中 boardList
的更改。
我在这里做了一个例子,它似乎按预期工作: https://codesandbox.io/s/sharp-mclaren-pv2s2?fontsize=14&hidenavigation=1&theme=dark
在我链接的代码中,我们不再传递位置状态的列表,而是将 boardList
传递给 BoardPage
并根据 [= 从那里提取正确的板16=]。这使我们能够始终访问当前状态。
当然有多种方法可以做到这一点,您可能更喜欢与我的示例不同的风格。只需确保传递实际状态而不是创建对它的单独引用。