简体   繁体   English

传播运算符不适用于 setState(React、Typescript 和 Material-ui)

[英]Spread operator not working with setState (React, Typescript & Material-ui)

I made a custom Snackbar component in React using Material-ui (and Typescript).我使用 Material-ui(和 Typescript)在 React 中制作了一个自定义的 Snackbar 组件。 I have trouble understanding how the spread operator and await work.我无法理解扩展运算符和等待的工作方式。 (Example here: https://codesandbox.io/s/gifted-hopper-tkkpi?file=/src/App.tsx:1311-1380 ) (这里的例子: https : //codesandbox.io/s/gifted-hopper-tkkpi?file=/src/App.tsx : 1311-1380

Full code:完整代码:

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

I trigger the Snackbar to open with我触发 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"});  
}

The Snackbar automatically closes after a set amount of Time. Snackbar 会在一段时间后自动关闭。 "open" only triggers the Snackbar and doesn't control the setOpen-state of the Snackbar. "open" 只触发 Snackbar,不控制 Snackbar 的 setOpen-state。

Now I tried:现在我试过:

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

This successfully triggers the Snackbar but the message/text is lost.这成功触发了 Snackbar,但消息/文本丢失。
Question 1: Shouldn't "...snackbar" still have "message: 'Snackbar' " saved?问题 1:“...snackbar”不应该仍然保存“message: 'Snackbar'”吗?

For some reason i also have to make the function async and await the first "setSnackbar" if I don't use "await fetch(Stuff...):出于某种原因,如果我不使用“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 });
}

Altough the await on "setSnackbar" is apparently not needed if you first have another line of code you await.如果您首先等待另一行代码,则显然不需要等待“setSnackbar”。 In my use case this works:在我的用例中,这有效:

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

}

Question 2: Can someone explain why the await is required/not required?问题 2:有人可以解释为什么需要/不需要等待吗?

The main problem in your implementation is that setSnackbar is handled as if it is synchronous, but it "kinda" isn't.您实现中的主要问题是setSnackbar被处理为好像它是同步的,但它“有点”不是。 It is thoroughly described here why: useState set method not reflecting change immediately (especially the second answer)这里详细描述了为什么: useState set 方法不会立即反映更改(尤其是第二个答案)

Answer 1:答案 1:

The spread operator doesn't work, because most of the times, the first setSnackbar is not completed.扩展运算符不起作用,因为大多数情况下,第一个 setSnackbar 未完成。

In order to fix this, you should either use the useEffect method passing the open attribute of the snackback object as a dependency in the array.为了解决这个问题,您应该使用useEffect方法将零食对象的open属性作为数组中的依赖项传递。

You should do the same thing as you did in your CustomSnackbar:您应该像在 CustomSnackbar 中一样做同样的事情:

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

Notice the _snackbar , it is used used in the callback of setSnackbar to get the at-that-time value of the open attribute.注意_snackbar ,它用于 setSnackbar 的回调中,以获取 open 属性的当时值。

Answer 2: About the await.答案 2:关于等待。

You need the await for api calls because api calls are asynchronous/Promises, meaning that the program will execute the next command before they are completed, thus you need to "await" for them, or use .then() to do stuff when they are finished.您需要await api 调用,因为 api 调用是异步/承诺,这意味着程序将在它们完成之前执行下一个命令,因此您需要“等待”它们,或者使用 .then() 在它们出现时执行操作完成。

The setSnackbar is not a promise, but yet it does not take effect immediately, that is why React has callbacks for setState and useEffect . setSnackbar不是承诺,但它不会立即生效,这就是 React 为 setState 和useEffect提供回调的useEffect To handle such cases.处理此类案件。 So, using await before the setSnackbar makes not difference因此,在setSnackbar之前使用await没有区别

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM