【问题标题】:ReactJS - Updating the state causes lagReactJS - 更新状态会导致延迟
【发布时间】:2021-09-20 17:16:14
【问题描述】:

说明

我有一个小应用程序,它使用useCallback() 挂钩来更新状态,但每次我这样做时,都会导致页面滞后。我的意思是实际延迟,而不仅仅是“等待异步setState()”延迟。

我的理论是更新状态会重新渲染太多组件,因为如果我将状态减少到更少的值,滞后就会消失。

基本上,我担心我更新状态的方式不会隔离我想要的值。

代码

我将 repo 加载到 CodeSandbox 中:https://codesandbox.io/s/long-forest-y9cdj?file=/src/App.js

这里是要点:

  1. 页面上的按钮有一个onClick,从App.js传下来

  2. App.js 中,我们生成handleClick 回调,它调用reducer

  3. reducer.js 中,"UPDATE_COUNT" 情况会更新状态以设置正确的值。

重现问题

乱七八糟地以有点快的速度点击一些按钮,你应该会遇到一些延迟。您知道,导致按钮动画冻结以及光标停留在“指针”模型中的延迟类型。

问题

状态更新能否以减少这种滞后的方式完成?

【问题讨论】:

    标签: javascript reactjs performance


    【解决方案1】:

    我发现唯一奇怪的是您将 useStyles 挂钩 inside 您的组件,因此每个渲染周期都会创建新样式.将它们移到组件之外。

    例子:

    App.js

    const useStyles = makeStyles((theme) => ({
        root: {
            flexGrow: 1
        },
        paper: {
            padding: theme.spacing(0),
            textAlign: "center",
            color: theme.palette.text.primary,
            backgroundColor: theme.palette.background
        }
    }));
    
    function App() {
      const classes = useStyles();
    
      const { state, dispatch } = React.useContext(MainContext);
    
      const handleClick = useCallback(
        (areaName, monsterName, newCount) => {
          newCount = newCount > 10 ? 10 : newCount;
          newCount = newCount < 0 ? 0 : newCount;
    
          dispatch({
            type: "UPDATE_COUNT",
            area: areaName,
            monster: monsterName,
            count: newCount
          });
        },
        [dispatch]
      );
    
      const areaObjects = Object.entries(state).map(([area, monster]) => {
        const monsters = Object.entries(monster).map(([monsterName, count]) => (
          <Grid item xs={12} key={monsterName}>
            <Monster
              area={area}
              name={monsterName}
              count={count}
              handleClick={handleClick}
            />
          </Grid>
        ));
    
        return (
          <Grid item xs={12} sm={6} md={4} lg={3} xl={2} key={area}>
            <Paper className={classes.paper}>
              <Grid item xs={12}>
                <h3>{area}</h3>
              </Grid>
              {monsters}
            </Paper>
          </Grid>
        );
      });
    
      return (
        <div className={classes.root} id={"app-container"} key={"app-container"}>
          <Grid container spacing={0}>
            {areaObjects}
          </Grid>
        </div>
      );
    }
    

    Monster.js

    const useStyles = makeStyles((theme) => ({
      root: {
        flexGrow: 1
      },
      addButtonContainer: {
        backgroundColor: "lightblue"
      },
      subtractButtonContainer: {
        backgroundColor: "lightyellow"
      },
      maxButtonContainer: {
        backgroundColor: "lightgreen"
      },
      monsterName: {
        textAlign: "center"
      },
      row: {
        backgroundColor: "lightgray"
      },
      rowSuccess: {
        backgroundColor: "lightgreen"
      }
    }));
    
    export default function Monster({ area, name, count, handleClick }) {
      const handleIncrement = (areaName, monsterName) => {
        handleClick(areaName, monsterName, count + 1);
      };
    
      const handleDecrement = (areaName, monsterName) => {
        handleClick(areaName, monsterName, count - 1);
      };
    
      const handleMaxout = (areaName, monsterName) => {
        handleClick(areaName, monsterName, 10);
      };
    
      const classes = useStyles();
      const rowClass = count === 10 ? classes.rowSuccess : classes.row;
    
      return (
        <Grid container item className={rowClass}>
          <Grid item xs={7} className={classes.monsterName}>
            {name}
          </Grid>
          <Grid item xs={1} className={classes.maxButtonContainer}>
            <IconButton
              color={"primary"}
              variant={"contained"}
              size={"small"}
              onClick={() => handleMaxout(area, name)}
            >
              <ArrowUpward fontSize={"small"} />
            </IconButton>
          </Grid>
          <Grid item xs={1} className={classes.addButtonContainer}>
            <IconButton
              color={"primary"}
              variant={"contained"}
              size={"small"}
              onClick={() => handleIncrement(area, name)}
            >
              <Add fontSize={"small"} />
            </IconButton>
          </Grid>
          <Grid item xs={2}>
            {count}
          </Grid>
          <Grid item xs={1} className={classes.subtractButtonContainer}>
            <IconButton
              color={"secondary"}
              variant={"contained"}
              size={"small"}
              onClick={() => handleDecrement(area, name)}
            >
              <Remove fontSize={"small"} />
            </IconButton>
          </Grid>
        </Grid>
      );
    }
    

    我还记住了上下文提供的state,尽管我不完全认为这部分是必要的。

    index.js
    
    import React, { useMemo } from "react";
    
    import initialState from "./initialState";
    import reducer from "./reducer";
    
    const MainContext = React.createContext();
    
    function MainContextProvider(props) {
      const [state, dispatch] = React.useReducer(reducer, initialState);
      const value = useMemo(() => ({ state, dispatch }), [state]);
    
      return (
        <MainContext.Provider value={value}>{props.children}</MainContext.Provider>
      );
    }
    
    const MainContextConsumer = MainContext.Consumer;
    
    export { MainContext, MainContextProvider, MainContextConsumer };
    

    【讨论】:

    • 在我这边大哎呀。这解决了滞后问题,是的。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-25
    • 1970-01-01
    • 2021-03-06
    • 2013-06-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多