为啥组件在单击和状态更改时会重新渲染?

Posted

技术标签:

【中文标题】为啥组件在单击和状态更改时会重新渲染?【英文标题】:Why does Component re-renders on Click and state change?为什么组件在单击和状态更改时会重新渲染? 【发布时间】:2020-08-14 15:04:34 【问题描述】:

当你在 onClick 中改变状态时,为什么整个 react 组件会重新渲染?

例如:https://codesandbox.io/s/vibrant-firefly-sgk5g?file=/src/App.js

当您单击数字时,整个组件会重新渲染,如果您从 on click 函数中删除 setCount,它就可以正常工作

组件背后的想法是为您单击的数字添加一个“活动”类,它更新了一个随机计数器,该计数器阻止添加“活动”类,因为它重新呈现整个组件

编辑:这里也有代码

import React,  useState  from "react";

const Hours = () => 
  const days = [1, 2, 3, 4, 5, 6];
  const [count, setCount] = useState(1);

  const TestClick = (e, item) => 
    setCount(count + 1);
    e.currentTarget.className = "active";
  ;

  const HandleHours = () => 
    let block = <span />;
    if (days) 
      block = days.map((hour, index) => 
        return (
          <span
            style= display: "block" 
            onClick=e => 
              TestClick(e, hour);
            
            className=`col-md-4` key=index>
            hour
          </span>
        );
      );
    
    return block;
  ;

  return (
    <div>
      <HandleHours />
    </div>
  );
;

export default Hours;

【问题讨论】:

您在寻找不同的答案吗? 【参考方案1】:

这里的问题不是来自 HandleHours 组件呈现的事实,而是因为每次更改 Hours 组件中的状态时它都会重新安装。

发生这种情况是因为HandleHours 被定义为Hours 组件中的一个组件,并且每次Hours 重新渲染一个对HandleHours 的新引用时,傻瓜的反应是认为该组件与DOM 分离并且一个新的组件替换它,因为它本质上是在参考上工作的。

现在当你渲染 HandleHours 时像

<div>
   HandleHours () 
</div>

HandleHours 突然从一个组件变成了一个返回 JSX 的函数,所以这次 Hours 组件重新渲染,即使对 HandleHours 的函数引用已经改变。它返回带有 key prop 的 JSX,它保持不变,因此 React 将其视为重新渲染,并且对 DOM 元素的小时更改不会丢失


现在第一种方法也有解决方案

您需要做的就是在您的 Hours 组件之外创建一个组件 HandleHours 并通过传递所需的 props 来呈现它

import React,  useState  from "react";
import "./styles.css";

const HandleHours = ( days, TestClick ) => 
  let block = <span />;
  if (days) 
    block = days.map((hour, index) => 
      return (
        <span
          style= display: "block" 
          onClick=e => 
            TestClick(e, hour);
          
          className=`col-md-4`
          key=index
        >
          hour
        </span>
      );
    );
  
  return block;
;

const days = [1, 2, 3, 4, 5, 6];
const Hours = () => 
  const [count, setCount] = useState(1);

  const TestClick = (e, item) => 
    setCount(count + 1);
    console.log("TestClick");
    e.currentTarget.className = "active";
  ;

  return (
    <div>
      <HandleHours days=days TestClick=TestClick />
    </div>
  );
;

export default Hours;

当您这样做时,HandleHours 组件不会重新安装在 Hours 组件的每次重新渲染上,它会正确维护 DOM 元素。

Here is a working demo for the second approach

【讨论】:

【参考方案2】:

这是当组件状态改变时 react 重新渲染的方式。状态钩子会在调用 setState 函数时重新呈现它所在的整个组件,这是 useState 返回的数组中的第二个元素。

如果你想在点击时改变一个元素的类,你需要把它存储为一个状态。在您的代码中,clicked span 的类在单击时更新,但在此之后组件会重新呈现并设置为 HandleHours 返回的内容。

我可能会有一个状态来跟踪点击了哪一天并相应地呈现(不知道为什么需要计数,但我把它留在那里):

import React,  useState  from "react";
const Hours = () => 
  const days = [1, 2, 3, 4, 5, 6];
  const [count, setCount] = useState(1);
  const [clickedDays, setClickedDays] = useState([]); // Added clickedDays state

  const TestClick = (e, item, isDayClicked) => 
    setCount(count + 1);
    if (!isDayClicked)  // Setting clicked days if they are not in the array yet
      setClickedDays([...clickedDays, item])
    
  ;

  const HandleHours = () => 
    let block = <span />;
    if (days) 
      block = days.map((hour, index) => 
        const isDayClicked = clickedDays.includes(hour);
        return (
          <span
            style= display: "block" 
            onClick=e => 
              TestClick(e, hour, isDayClicked);
            
            className=isDayClicked ? 'active' : 'col-md-4' // Setting different class depending on state
            key=index
          >
            hour
          </span>
        );
      );
    
    return block;
  ;

  return (
    <div>
      <HandleHours />
    </div>
  );
;

export default Hours;

【讨论】:

计数是为了显示我面临的问题,但我发现了问题,看看我的答案

以上是关于为啥组件在单击和状态更改时会重新渲染?的主要内容,如果未能解决你的问题,请参考以下文章

即使道具没有改变,为啥还要对重新渲染组件做出反应?

更改数组中的一个状态会导致在 React Hooks 中重新渲染整个循环生成的自定义组件

我的 Redux 状态发生了变化,为啥 React 没有触发重新渲染?

React 功能组件在重新挂载后停止渲染状态更改

state 用作组件中的 props,状态更改不会重新渲染组件

React 子组件不会在其状态更改时重新渲染