Uncaught Invariant Violation:渲染的 hooks 比上次渲染时多

Uncaught Invariant Violation: Rendered more hooks than during the previous render

我有一个看起来像这样的组件(非常简化的版本):

const component = (props: PropTypes) => {

    const [allResultsVisible, setAllResultsVisible] = useState(false);

    const renderResults = () => {
        return (
            <section>
                <p onClick={ setAllResultsVisible(!allResultsVisible) }>
                    More results v
                </p>
                {
                    allResultsVisible &&
                        <section className="entity-block--hidden-results">
                            ...
                        </section>
                }
            </section>
        );
    };

    return <div>{ renderResults() }</div>;
}

当我加载使用该组件的页面时,出现此错误:Uncaught Invariant Violation: Rendered more hooks than during the previous render.我试图找到对此错误的解释,但我的搜索没有返回任何结果。

当我稍微修改组件时:

const component = (props: PropTypes) => {

    const [allResultsVisible, setAllResultsVisible] = useState(false);

    const handleToggle = () => {
        setAllResultsVisible(!allResultsVisible);
    }

    const renderResults = () => {
        return (
            <section>
                <p onClick={ handleToggle }>
                    More results v
                </p>
                {
                    allResultsVisible &&
                        <section className="entity-block--hidden-results">
                            ...
                        </section>
                }
            </section>
        );
    };

    return <div>{ renderResults() }</div>;
}

我不再收到该错误。是因为我在 renderResults 返回的 jsx 中包含了 setState 函数吗?如果能解释为什么修复有效,那就太好了。

修复之所以有效,是因为第一个代码示例(出错的代码示例)调用了 onClick 中的函数,而第二个(有效的代码示例)将函数传递给 onClick。不同之处在于那些非常重要的括号,在 JavaScript 中表示 'invoke this code'.

这样想:在第一个代码示例中,每次呈现 component 时,都会调用 renderResults。每次发生这种情况时,都会调用 setAllResultsVisible(!allResultsVisible),而不是等待点击。由于 React 按照自己的时间表执行渲染,因此无法确定会发生多少次。

来自 React 文档:

With JSX you pass a function as the event handler, rather than a string.

React Handling Events Docs

注意:当 运行 沙盒中的第一个代码示例时,我无法得到这个确切的错误消息。我的错误是指无限循环。也许更新版本的 React 会产生所描述的错误?

您只需更改您的 onclick 事件,在 setAllResultsVisible

之前添加 () =>
<p onClick={() => setAllResultsVisible(!allResultsVisible) }> 
    More results v
</p>

它将完美运行

即使在进行了上述修复之后,此错误还有其他一些原因。我在下面写了一个发生在我身上的用例。

function Comp(props){return <div>{props.val}</div>}

该组件在jsx中可以通过以下方式调用:

1. <Comp val={3} /> // works well
2. { Comp({val:3}) } // throws uncaught invariant violation error, at least it throw in my case, may be there were other factors, but changing back to first way removed that problem

看到问题可以React :

  1. 渲染的钩子比以前的渲染少。
  2. 渲染了比之前渲染更多的钩子。

在这两种情况下,事情可能就像你有一个条件语句调用相同的函数,returns 从不同的地方呈现,就像都包裹在父 return 函数中:

const parentFunc = () => {
    if(case==1)
        return function_a();
    if (case==2)
        return function_b();
}

现在 function_a() 可能是一个创建两个或一个钩子的函数假设 useStyle() 或其他任何东西

和 function_b() 可能是一个不创建钩子的函数。

现在,当 parentFunc returns function_a() 渲染一个 hook 而 function_b() 渲染没有 hook 然后反应会告诉你从同一个渲染函数两个不同的渲染return一个有两个或一个钩子,另一个有一个钩子,这种差异导致了错误。错误

less hooks were rendered. And the error is quite obvious.

当情况相反并且 function_b() 是 returned 条件的第一个原因然后反应会告诉你从相同的渲染函数不同的渲染是 returned 和错误将是 .

Rendered more hooks than previous render.

现在,解决方案:

更改代码流,例如创建 function_ab(),这将确保所有正在使用的挂钩都在该函数中呈现:

const function_ab = () => {
    if(case==1)
         return (<div></div>) //or whatever
    if(case==2)
         return (<div>I am 2 </div>) //or whatever
}

问题出在 onClick 中,因为 setAllResultsVisible 被调用,它会在每次渲染时触发状态更改和结果

onClick={ setAllResultsVisible(!allResultsVisible) }

将其更改为函数调用:

onClick={_ => setAllResultsVisible(!allResultsVisible) }

我遇到了同样的问题。我所做的是这样的:

const Table = (listings) => {

    const {isLoading} = useSelector(state => state.tableReducer);

    if(isLoading){
        return <h1>Loading...</h1>
    }

    useEffect(() => {
       console.log("Run something")
    }, [])

    return (<table>{listings}</table>)
}

我认为发生的事情是在第一次渲染时,组件提前返回而 useEffect 没有 运行。当 isLoading 状态改变时,useEffect 运行 并且我得到了错误 - 钩子渲染的次数比之前的渲染次数多。

一个简单的更改修复了它:

const Table = (listings) => {
    
    const {isLoading} = useSelector(state => state.tableReducer);
        
    useEffect(() => {
        console.log("Run something")
    }, [])
    
    if(isLoading){
        return <h1>Loading...</h1>
    }
    return (<table>{listings}</table>)
}