代码之家  ›  专栏  ›  技术社区  ›  Slim

未捕获的不变量冲突:重新渲染过多。React限制渲染的数量以防止无限循环

  •  1
  • Slim  · 技术社区  · 6 年前

    我试图添加一个snackBar,以便在用户登录或不登录时显示消息。

    import React from "react";
    import PropTypes from "prop-types";
    import classNames from "classnames";
    import CheckCircleIcon from "@material-ui/icons/CheckCircle";
    import ErrorIcon from "@material-ui/icons/Error";
    import CloseIcon from "@material-ui/icons/Close";
    import green from "@material-ui/core/colors/green";
    import IconButton from "@material-ui/core/IconButton";
    import Snackbar from "@material-ui/core/Snackbar";
    import SnackbarContent from "@material-ui/core/SnackbarContent";
    import { withStyles } from "@material-ui/core/styles";
    
    const variantIcon = {
      success: CheckCircleIcon,
      error: ErrorIcon
    };
    
    const styles1 = theme => ({
      success: {
        backgroundColor: green[600]
      },
      error: {
        backgroundColor: theme.palette.error.dark
      },
      icon: {
        fontSize: 20
      },
      iconVariant: {
        opacity: 0.9,
        marginRight: theme.spacing.unit
      },
      message: {
        display: "flex",
        alignItems: "center"
      }
    });
    
    function SnackbarContentWrapper(props) {
      const { classes, className, message, onClose, variant, ...other } = props;
      const Icon = variantIcon[variant];
    
      return (
        <SnackbarContent
          className={classNames(classes[variant], className)}
          aria-describedby="client-snackbar"
          message={(
            <span className={classes.message}>
              <Icon className={classNames(classes.icon, classes.iconVariant)} />
              {message}
            </span>
          )}
          action={[
            <IconButton
              key="close"
              aria-label="Close"
              color="inherit"
              className={classes.close}
              onClick={onClose}
            >
              <CloseIcon className={classes.icon} />
            </IconButton>
          ]}
          {...other}
        />
      );
    }
    
    SnackbarContentWrapper.propTypes = {
      classes: PropTypes.shape({
        success: PropTypes.string,
        error: PropTypes.string,
        icon: PropTypes.string,
        iconVariant: PropTypes.string,
        message: PropTypes.string,
      }).isRequired,
      className: PropTypes.string.isRequired,
      message: PropTypes.node.isRequired,
      onClose: PropTypes.func.isRequired,
      variant: PropTypes.oneOf(["success", "error"]).isRequired
    };
    
    const MySnackbarContentWrapper = withStyles(styles1)(SnackbarContentWrapper);
    
    const CustomizedSnackbar = ({
      open,
      handleClose,
      variant,
      message
    }) => {
      return (
        <div>
          <Snackbar
            anchorOrigin={{
              vertical: "bottom",
              horizontal: "left"
            }}
            open={open}
            autoHideDuration={6000}
            onClose={handleClose}
          >
            <MySnackbarContentWrapper
              onClose={handleClose}
              variant={variant}
              message={message}
            />
          </Snackbar>
        </div>
      );
    };
    
    CustomizedSnackbar.propTypes = {
      open: PropTypes.bool.isRequired,
      handleClose: PropTypes.func.isRequired,
      variant: PropTypes.string.isRequired,
      message: PropTypes.string.isRequired
    };
    
    export default CustomizedSnackbar;
    

    SignInFormContainer.jsx:

    import React, { useState } from 'react';
    import PropTypes from 'prop-types';
    import { connect } from 'react-redux';
    import SnackBar from '../../components/SnackBar';
    import SignInForm from './SignInForm';
    
    const SingInContainer = ({ message, variant}) => {
        const [open, setSnackBarState] = useState(false);
        const handleClose = (reason) => {
            if (reason === 'clickaway') {
              return;
            }
            setSnackBarState(false)
    
          };
    
        if (variant) {
            setSnackBarState(true);
        }
        return (
            <div>
            <SnackBar
                open={open}
                handleClose={handleClose}
                variant={variant}
                message={message}
                />
            <SignInForm/>
            </div>
        )
    }
    
    SingInContainer.propTypes = {
        variant: PropTypes.string.isRequired,
        message: PropTypes.string.isRequired
    }
    
    const mapStateToProps = (state) => {
        const {variant, message } = state.snackBar;
    
        return {
            variant,
            message
        }
    }
    
    export default connect(mapStateToProps)(SingInContainer);
    

    运行应用程序时出现以下错误:

    Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop.
    at invariant (http://localhost:9000/bundle.js:34484:15)
    at dispatchAction (http://localhost:9000/bundle.js:47879:44)
    at SingInContainer (http://localhost:9000/bundle.js:79135:5)
    at renderWithHooks (http://localhost:9000/bundle.js:47343:18)
    at updateFunctionComponent (http://localhost:9000/bundle.js:49010:20)
    at beginWork (http://localhost:9000/bundle.js:50020:16)
    at performUnitOfWork (http://localhost:9000/bundle.js:53695:12)
    at workLoop (http://localhost:9000/bundle.js:53735:24)
    at HTMLUnknownElement.callCallback (http://localhost:9000/bundle.js:34578:14)
    at Object.invokeGuardedCallbackDev (http://localhost:9000/bundle.js:34628:16)
    

    useState 钩子以改变钩条的状态。我应该用一个班级和一个 componentShouldUpdate 为了不渲染多次?

    1 回复  |  直到 6 年前
        1
  •  31
  •   GregL    6 年前

    我怀疑问题在于,您正在函数组件体内立即调用您的状态设置器,这迫使React再次调用您的函数,使用相同的道具,最后再次调用状态设置器,触发React再次调用您的函数。。。。等等。

    const SingInContainer = ({ message, variant}) => {
        const [open, setSnackBarState] = useState(false);
        const handleClose = (reason) => {
            if (reason === 'clickaway') {
              return;
            }
            setSnackBarState(false)
    
          };
    
        if (variant) {
            setSnackBarState(true); // HERE BE DRAGONS
        }
        return (
            <div>
            <SnackBar
                open={open}
                handleClose={handleClose}
                variant={variant}
                message={message}
                />
            <SignInForm/>
            </div>
        )
    }
    

    相反,我建议您只使用三元值有条件地设置state属性的默认值,这样您就可以得到:

    const SingInContainer = ({ message, variant}) => {
        const [open, setSnackBarState] = useState(variant ? true : false); 
                                      // or useState(!!variant); 
                                      // or useState(Boolean(variant));
        const handleClose = (reason) => {
            if (reason === 'clickaway') {
              return;
            }
            setSnackBarState(false)
    
          };
    
        return (
            <div>
            <SnackBar
                open={open}
                handleClose={handleClose}
                variant={variant}
                message={message}
                />
            <SignInForm/>
            </div>
        )
    }
    

    综合演示

    看这个 CodeSandbox.io demo 对于一个全面的演示,它的工作,加上你的坏组件,你可以在两者之间切换。

        2
  •  11
  •   Josh Pittman    6 年前

    SnackbarContentWrapper 你需要改变

    <IconButton
              key="close"
              aria-label="Close"
              color="inherit"
              className={classes.close}
              onClick={onClose}
            >
    

    <IconButton
              key="close"
              aria-label="Close"
              color="inherit"
              className={classes.close}
              onClick={() => onClose}
            >
    
    

    相反,你可以用咖喱 handleClose 在里面 SingInContainer

    const handleClose = () => (reason) => {
            if (reason === 'clickaway') {
              return;
            }
            setSnackBarState(false)
    
          };
    

        3
  •  2
  •   Elyson Romeiro    5 年前

    必须在onClick中链接事件。另外,click函数必须接收事件。看这个例子

    export default function Component(props) {
    
        function clickEvent (event, variable){
            console.log(variable);
        }
    
        return (
            <div>
                <IconButton
                    key="close"
                    aria-label="Close"
                    color="inherit"
                    onClick={e => clickEvent(e, 10)}
                >
            </div>
        )
    }