【问题标题】:Spread operator not working with setState (React, Typescript & Material-ui)扩展运算符不适用于 setState(React、Typescript 和 Material-ui)
【发布时间】:2021-01-16 04:51:00
【问题描述】:

我使用 Material-ui(和 Typescript)在 React 中制作了一个自定义的 Snackbar 组件。我很难理解传播运算符和等待的工作方式。 (此处示例:https://codesandbox.io/s/gifted-hopper-tkkpi?file=/src/App.tsx:1311-1380

完整代码:

import React, { FunctionComponent, useEffect } from "react";
import Button from "@material-ui/core/Button";
import Snackbar from "@material-ui/core/Snackbar";
import { Color } from "@material-ui/lab";
import { Alert } from "@material-ui/lab";

interface SnackbarModel {
  open: boolean;
  severity?: Color;
  message: string;
}

const CustomSnackbar: FunctionComponent<SnackbarModel> = (props) => {
  const [opened, setOpened] = React.useState(false);

  useEffect(() => {
    if (props.open) {
      setOpened(true);
    }
  }, [props.open]);

  const handleClose = (event: React.ChangeEvent<object>, reason: string) => {
    if (reason === "clickaway") {
      return;
    }
    setOpened(false);
  };

  return (
    <div>
      <Snackbar
        open={opened}
        autoHideDuration={2000}
        onClose={handleClose}
        anchorOrigin={{ vertical: "top", horizontal: "center" }}
      >
        <Alert severity={props.severity}>{props.message}</Alert>
      </Snackbar>
    </div>
  );
};

export default function App() {
  const [snackbar, setSnackbar] = React.useState<SnackbarModel>({
    open: false,
    message: ""
  });

  async function handleClick() {
    
    await fetch(Stuff...)
    // Working as desired
    //setSnackbar({open: true, severity: "success", message: "Snackbar"});
    //setSnackbar({open: false, severity: "success", message: "Snackbar"});

    // Somehow not working
    setSnackbar({ open: true, severity: "success", message: "Snackbar" });
    setSnackbar({ ...snackbar, open: false });
  }

  return (
    <div>
      <CustomSnackbar
        open={snackbar.open}
        message={snackbar.message}
        severity={snackbar.severity}
      />

      <Button variant="contained" onClick={handleClick}>
        Snackbar
      </Button>
    </div>
  );
}

我触发 Snackbar 打开

const [snackbar, setSnackbar] = React.useState<SnackbarModel>({
   open: false,
   message: ""
});


async function handleClick() {
   
   await fetch(Stuff)
   setSnackbar({open: true, severity: "success", message: "Snackbar"}) 
   setSnackbar({open: false, severity: "success", message: "Snackbar"});  
}

Snackbar 会在设定的时间后自动关闭。 “open”只触发Snackbar,不控制Snackbar的setOpen-state。

现在我尝试了:

setSnackbar({ open: true, severity: "success", message: "Snackbar" });
console.log(snackbar); //gives you: Object {open: false, message: ""}
setSnackbar({ ...snackbar, open: false });  

这成功触发了 Snackbar,但消息/文本丢失了。
问题1:“...snackbar”不应该还保存“message: 'Snackbar'”吗?

由于某种原因,如果我不使用“await fetch(Stuff...)”,我还必须使函数异步并等待第一个“setSnackbar”:

async function handleClick() {

  //working as desired
  await setSnackbar({open: true, severity: "success", message: "Snackbar"});
  setSnackbar({open: false, severity: "success", message: "Snackbar"});

  // OR

  //not working as desired
  await setSnackbar({ open: true, severity: "success", message: "Snackbar" });
  setSnackbar({ ...snackbar, open: false });
}

如果您首先等待另一行代码,显然不需要“setSnackbar”上的等待。在我的用例中,这是可行的:

async function handleClick() {
  
  await fetch(Stuff...)
  setSnackbar({open: true, severity: "success", message: "Snackbar"});
  setSnackbar({open: false, severity: "success", message: "Snackbar"});

}

问题 2:有人可以解释为什么需要/不需要等待吗?

【问题讨论】:

    标签: javascript html reactjs typescript material-ui


    【解决方案1】:

    您的实现中的主要问题是setSnackbar 被处理为好像它是同步的,但它“有点”不是。这里详细描述了为什么:useState set method not reflecting change immediately(尤其是第二个答案)

    答案 1:

    spread 运算符不起作用,因为大多数时候,第一个 setSnackbar 没有完成。

    为了解决这个问题,您应该使用useEffect 方法将snackback 对象的open 属性作为数组中的依赖项传递。

    您应该像在 CustomSnackbar 中那样做同样的事情:

    useEffect(() => {
      if (snackbar.open) {
        setSnackbar((_snackbar) => ({ ..._snackbar, open: false }));
      }
    }, [snackbar.open]);
    

    注意_snackbar,在setSnackbar的回调中使用它来获取open属性的当时值。

    答案2:关于等待。

    你需要await 来调用 api,因为 api 调用是异步的/Promises,这意味着程序将在它们完成之前执行下一个命令,因此你需要“等待”它们,或者使用 .then()完成后做一些事情。

    setSnackbar 不是一个承诺,但它不会立即生效,这就是为什么 React 有 setState 和 useEffect 的回调。处理此类案件。所以,在setSnackbar 之前使用await 没有区别

    【讨论】:

    • 非常感谢您的回答 :) 您能否帮助我了解如何实现您的 useEffect。我尝试将它放在 handleClick 中,但这给了我“打破钩子规则”的错误。我也尝试将它放在 handleClick 之后,但 useEffect 继续循环。我尝试用 [] 替换 [snackbar.open] 以使其仅触发一次,但没有解决问题。抱歉,如果有明显的实现方式。我只有几周的编码经验。
    • 不,你是对的!我发布的 useEffect 是自触发的,会导致无限循环。让我解决它
    • 太棒了!在handleClick 函数起作用之后放置更新的那个。非常感谢!
    猜你喜欢
    • 2023-03-25
    • 2019-08-10
    • 1970-01-01
    • 1970-01-01
    • 2020-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-22
    相关资源
    最近更新 更多