在我的 "useState" 不断更新时作出反应 re-render
React re-render when my "useState" constant updates
我开始学习 React,更具体地说是 ReactJS,现在我有了 NodeJS 后端,我在其中存储了一个项目数组,其中包含:id(uuid),url (字符串),技术(字符串数组),标题(字符串)和喜欢(数字)。
我的组件正在为我的后端发送到前端的每个项目渲染一个 div,每个 div 将包含该项目的所有属性,还有一个按钮在我的后端触发一条路线来增加喜欢的数量。它应该在单击按钮的同时更新该数字,我想这样做而不需要再次向 API 请求所有项目列表。
嗯,我的组件是这样的:
import React, { useContext, useEffect, useState } from 'react';
import { ThemeContext } from '../../providers/ThemeContext';
import { Button } from '@material-ui/core';
import axios from '../../services/axios';
function ListProjects() {
const [projects, setProjects] = useState<any[]>([]);
const { theme } = useContext(ThemeContext);
const updateProjects = async () => {
const res = await axios.get('list');
res.data.status === 1 && setProjects(res.data.projects);
};
useEffect(() => {
updateProjects();
}, []);
const onLikeProject = async (id: string) => {
const newObj = (await axios.post(`${id}/like`)).data.updatedProject;
const objIndex = projects.findIndex(i => i.id === id);
let projectsCopy = projects;
/* console.log(projects[objIndex]); */
projectsCopy[objIndex] = newObj;
/* console.log(projectsCopy[objIndex]); */
setProjects(projectsCopy);
/* console.log(projects[objIndex]); */
};
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
minHeight: 'calc(100vh - 64px)',
minWidth: '100vw',
backgroundColor: theme.bg,
color: theme.color
}}>
{ projects && projects.map((p, i) => <div key={p.id} style={{
display: 'flex',
flexDirection: 'column',
width: '550px',
height: '300px',
border: '1px solid blue',
marginBottom: '10px'
}}>
<h3>Title: { p.title }</h3>
<h3>URL: { p.url }</h3>
<h3>Techs: { p.techs.map((t: any, i: any) => i+1 < p.techs.length ? `${t}, ` : t) }</h3>
<h3>Likes: { p.likes }</h3>
<Button color="primary" variant="contained" onClick={() => onLikeProject(p.id) }>Like</Button>
</div>) }
</div>
);
}
export default ListProjects;
如您所见,当页面加载时,项目状态会根据 API 列表响应进行更新。我已经尝试使用相同的 updateProjects()
函数在用户单击 Like 按钮时更新列表,但这会触发另一个 api 调用,这会延迟一点,并使 api 列出每个类似请求的请求,将打破服务可用请求的可能限制。所以我尝试了 onLikeProject()
中的代码,但它不会更新 UI,即使正在更新项目数组。
我该如何进行?
谢谢
问题
状态突变。 projectsCopy
是对状态对象 projects
的引用,它会发生变异并保存回状态,因此状态数组引用永远不会更新,React 会在任何重新渲染时退出。
const onLikeProject = async (id: string) => {
const newObj = (await axios.post(`${id}/like`)).data.updatedProject;
const objIndex = projects.findIndex(i => i.id === id);
let projectsCopy = projects;
projectsCopy[objIndex] = newObj; // <-- state mutation
setProjects(projectsCopy);
};
解决方案
将状态对象浅复制到新的数组引用中。
const onLikeProject = async (id: string) => {
const newObj = (await axios.post(`${id}/like`)).data.updatedProject;
const objIndex = projects.findIndex(i => i.id === id);
let projectsCopy = [...projects]; // <-- shallow copy state array
projectsCopy[objIndex] = newObj; // <-- then update index
setProjects(projectsCopy);
};
更规范的方法是将前一个状态映射到下一个状态。
const onLikeProject = async (id: string) => {
const newObj = (await axios.post(`${id}/like`)).data.updatedProject;
setProjects(projects => projects.map((project) => project.id === id ? newObj : project));
};
我开始学习 React,更具体地说是 ReactJS,现在我有了 NodeJS 后端,我在其中存储了一个项目数组,其中包含:id(uuid),url (字符串),技术(字符串数组),标题(字符串)和喜欢(数字)。
我的组件正在为我的后端发送到前端的每个项目渲染一个 div,每个 div 将包含该项目的所有属性,还有一个按钮在我的后端触发一条路线来增加喜欢的数量。它应该在单击按钮的同时更新该数字,我想这样做而不需要再次向 API 请求所有项目列表。
嗯,我的组件是这样的:
import React, { useContext, useEffect, useState } from 'react';
import { ThemeContext } from '../../providers/ThemeContext';
import { Button } from '@material-ui/core';
import axios from '../../services/axios';
function ListProjects() {
const [projects, setProjects] = useState<any[]>([]);
const { theme } = useContext(ThemeContext);
const updateProjects = async () => {
const res = await axios.get('list');
res.data.status === 1 && setProjects(res.data.projects);
};
useEffect(() => {
updateProjects();
}, []);
const onLikeProject = async (id: string) => {
const newObj = (await axios.post(`${id}/like`)).data.updatedProject;
const objIndex = projects.findIndex(i => i.id === id);
let projectsCopy = projects;
/* console.log(projects[objIndex]); */
projectsCopy[objIndex] = newObj;
/* console.log(projectsCopy[objIndex]); */
setProjects(projectsCopy);
/* console.log(projects[objIndex]); */
};
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
minHeight: 'calc(100vh - 64px)',
minWidth: '100vw',
backgroundColor: theme.bg,
color: theme.color
}}>
{ projects && projects.map((p, i) => <div key={p.id} style={{
display: 'flex',
flexDirection: 'column',
width: '550px',
height: '300px',
border: '1px solid blue',
marginBottom: '10px'
}}>
<h3>Title: { p.title }</h3>
<h3>URL: { p.url }</h3>
<h3>Techs: { p.techs.map((t: any, i: any) => i+1 < p.techs.length ? `${t}, ` : t) }</h3>
<h3>Likes: { p.likes }</h3>
<Button color="primary" variant="contained" onClick={() => onLikeProject(p.id) }>Like</Button>
</div>) }
</div>
);
}
export default ListProjects;
如您所见,当页面加载时,项目状态会根据 API 列表响应进行更新。我已经尝试使用相同的 updateProjects()
函数在用户单击 Like 按钮时更新列表,但这会触发另一个 api 调用,这会延迟一点,并使 api 列出每个类似请求的请求,将打破服务可用请求的可能限制。所以我尝试了 onLikeProject()
中的代码,但它不会更新 UI,即使正在更新项目数组。
我该如何进行? 谢谢
问题
状态突变。 projectsCopy
是对状态对象 projects
的引用,它会发生变异并保存回状态,因此状态数组引用永远不会更新,React 会在任何重新渲染时退出。
const onLikeProject = async (id: string) => {
const newObj = (await axios.post(`${id}/like`)).data.updatedProject;
const objIndex = projects.findIndex(i => i.id === id);
let projectsCopy = projects;
projectsCopy[objIndex] = newObj; // <-- state mutation
setProjects(projectsCopy);
};
解决方案
将状态对象浅复制到新的数组引用中。
const onLikeProject = async (id: string) => {
const newObj = (await axios.post(`${id}/like`)).data.updatedProject;
const objIndex = projects.findIndex(i => i.id === id);
let projectsCopy = [...projects]; // <-- shallow copy state array
projectsCopy[objIndex] = newObj; // <-- then update index
setProjects(projectsCopy);
};
更规范的方法是将前一个状态映射到下一个状态。
const onLikeProject = async (id: string) => {
const newObj = (await axios.post(`${id}/like`)).data.updatedProject;
setProjects(projects => projects.map((project) => project.id === id ? newObj : project));
};