当我的“useState”不断更新时反应重新渲染

时间:2021-03-30 18:03:43

标签: javascript node.js reactjs use-state

我开始学习 React,更具体地说是 ReactJS,现在我有了我的 NodeJS 后端,我在其中存储了一个项目数组,其中包含:id(uuid)、url(string)、techs(字符串数组)、标题(字符串)和喜欢(数字)。

我的组件正在为后端发送到前端的每个项目渲染一个 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() 函数在用户单击“赞”按钮时更新列表,但这会触发另一个 api 调用,这会延迟一点,并对每个“赞”请求发出 api 列表请求, 将打破服务可用请求的可能限制。所以我尝试了 onLikeProject() 中的代码,但它不会更新 UI,即使项目数组正在更新。

我应该如何进行? 谢谢

1 个答案:

答案 0 :(得分:2)

问题

状态突变。 \b\w+: +(.(?! +\\w+:))+ 是对状态对象 projectsCopy 的引用,它被改变并保存回状态,因此状态数组引用永远不会更新,并且 React 会放弃任何重新渲染。

projects

解决方案

将状态对象浅拷贝到一个新的数组引用中。

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);
};