【问题标题】:Avoid component to rerender at state change with hooks使用钩子避免组件在状态更改时重新渲染
【发布时间】:2021-03-25 19:15:00
【问题描述】:

我有一个Layout组件,里面有一个Table组件,每行都有一个组件Entry。

可以选择每一行,所以稍后我可以通过一个按钮将所有条目发送到 REST 服务,因此每次选择一行时,我都会将其添加到我的状态中。

但是每次状态变化时,我的 Layout 组件都会渲染,渲染表格的每个条目,这让我损失了很多性能和时间。

有没有办法避免重新渲染组件?我试图避免使用类组件。

这个函数触发渲染...

 const checkBoxHandler = (index) => {
    
    
    const actualSelectedCheck = checks[index]

    if(!selectedChecks.includes(actualSelectedCheck)){
        setSelectedChecks(selectedChecks.concat(actualSelectedCheck))
    } else {
        const newSelectedChecks = selectedChecks.slice();
        const indexOfSelected = selectedChecks.indexOf(actualSelectedCheck)
        newSelectedChecks.splice(indexOfSelected, 1);
        setSelectedChecks(newSelectedChecks);
    }

}

为什么我使用选中的支票作为状态?因为 rest 服务的按钮只有在有 1 个或多个选中的检查时才会呈现。


正在重新渲染的 Mi 布局组件...

const fixedModal = (selectedChecks.length === 0) ? null : <RescueAdminModal selectedChecksLength={selectedChecks.length}/>;

const table =
(error === null) ? 
   (loading) ? <Spinner />
   : <RATable checkboxHandler={checkBoxHandler} checks={checks} /> : null;


return(
        <Aux>
            <div className="mb-2">Filtros por estado</div>
            <RAStates changeStateHandler={stateHandler} />
            {table}
            {fixedModal}
            <div style={{height:'6rem'}} />
        </Aux>
    )

我的表格组件;

 const RATable = (props) => {

  const classes = useStyles();

  return (
    <TableContainer style={{overflowX: "initial"}} className='my-5' component={Paper}>
      <Table stickyHeader className={classes.table} size="small" aria-label="a dense table">
        <TableHead>
          <TableRow>
            <TableCell align="center"><b>Fecha de presentación</b></TableCell>
            <TableCell align="center"><b>Bco. emisor</b></TableCell>
            <TableCell align="center"><b>Nro. Cheque</b></TableCell>
            <TableCell align="center"><b>Cta. emisora</b></TableCell>
            <TableCell align="center"><b>Importe</b></TableCell>
            <TableCell align="center"><b>Suc. Recep.</b></TableCell>
            <TableCell align="center"><b>Nro. Boleta</b></TableCell>
            <TableCell align="center"><b>Operatoria</b></TableCell>
            <TableCell align="center"><b>Fecha Rescate</b></TableCell>
            <TableCell align="center"><b>Estado</b></TableCell>
            <TableCell align="center"><b>Fec. Ingreso</b></TableCell>
            <TableCell align="center"></TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {props.checks.map((check, index) => <RACheckEntry checkboxHandler={props.checkboxHandler} check={check} index={index+"nico"}/>)}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

我的入口组件;

const RACheckEntry = (props) => {
    console.log("render checkentry")
    const toggleCheckBox = () => props.checkboxHandler(props.index);

    return(
        <TableRow key={props.index}>
            <TableCell align="center" component="th" scope="row">
                {props.check.fecPresentacion}
            </TableCell>
            <TableCell align="right">{props.check.bcoEmis}</TableCell>
            <TableCell align="right">{props.check.nroCheque}</TableCell>
            <TableCell align="right">{props.check.ctaEmisora}</TableCell>
            <TableCell align="right">{props.check.importe}</TableCell>
            <TableCell align="right">{props.check.sucRecep}</TableCell>
            <TableCell align="right">{props.check.nroBoleta}</TableCell>
            <TableCell align="right">{props.check.oper}</TableCell>
            <TableCell align="right">{props.check.fechaRescate}</TableCell>
            <TableCell align="right">{props.check.estado}</TableCell>
            <TableCell align="right">{props.check.fecIngreso}</TableCell>
            {(props.check.estado === A_RESCATAR) ? <Checkbox onChange={() => toggleCheckBox(props.index)} color="primary"/> : null}
        </TableRow>
          
    )

在 Nadia 发表评论后编辑:

const checkBoxHandler = React.useCallback(index => {
    
    
    const actualSelectedCheck = checks[index]

    if(!selectedChecks.includes(actualSelectedCheck)){
        setSelectedChecks(selectedChecks.concat(actualSelectedCheck))
    } else {
        const newSelectedChecks = selectedChecks.slice();
        const indexOfSelected = selectedChecks.indexOf(actualSelectedCheck)
        newSelectedChecks.splice(indexOfSelected, 1);
        setSelectedChecks(newSelectedChecks);
    }

}, []);

【问题讨论】:

  • 您应该包含更多我认为的组件代码,以便它成为可重现的示例。
  • 我刚刚添加了我所有的 JSX!
  • 你的TableContainerTableTableHeadTableBodyTableRowTableCell组件来自哪个库?
  • 他们来自material-ui。
  • 您可能希望将RACheckEntry 包裹在React.Memo 中,这样它们只会在propss 更改时重新渲染(如果这样做,请确保checkboxHandler 是使用useCallback 创建的)

标签: javascript reactjs react-hooks material-ui


【解决方案1】:

如果您将RACheckEntry 包装在React.memo (RACheckEntry =React.memo((props) =&gt; {..})) 中,React 只会在道具更改时重新渲染它,但是其中一个道具是方法checkboxHandler,我看不出您在哪里定义它,但如果它是在功能组件中定义的,它将在每次渲染时重新创建,从而使memo 无用。为了避免这个问题,React 提供了useCallback 钩子,如果你用它定义你的处理程序,它将在渲染之间保持不变(const checkboxHandler= useCallback(() =&gt; { ...},[])。

有人在使用不同的表时遇到了类似的问题,似乎对他们有用react-table is extremely slow with react-select: how to speed it up?

更新:使用 setSelectedChecks 回调中的状态移动所有操作,因此您不依赖于 checkBoxHandler 中的当前状态

const checkBoxHandler = React.useCallback(index => {

setSelectedChecks(selectedChecks => {
const actualSelectedCheck = checks[index]

if(!selectedChecks.includes(actualSelectedCheck)){
    return selectedChecks.concat(actualSelectedCheck)
} else {
    const newSelectedChecks = selectedChecks.slice();
    const indexOfSelected = selectedChecks.indexOf(actualSelectedCheck)
    newSelectedChecks.splice(indexOfSelected, 1);
    return newSelectedChecks;
}})
}, [checks]);

这是它的简化版https://codesandbox.io/s/trusting-franklin-euzn7?file=/src/App.js

更新 2:

它最初不起作用的原因是两个因素的结合:JavaScript 闭包和 React 不可变状态。当一个 JavaScript 函数被创建时,它被包围在周围的状态中(基本上可行的名称被替换为内存中的实际地址)。但是,当您在 React 中设置状态时,您不会修改内存中的现有对象,而是创建全新的对象。这意味着在第一次渲染中创建的checkboxHandler 被两个空数组卡住了,无法知道当前状态在内存中的位置。

要解决此问题,您可以将checksselectedChecks 作为参数传递给checkboxHandler,或者将它们作为依赖项添加到useCallback。在后一种情况下,每次依赖项之一发生更改时,都会重新创建checkboxHandler。现在,checks 很好,因为它们只更新一次,这将导致更新所有条目。但是,如果您将 selectedChecks 添加为依赖项,则每次更新 selectedChecks 时都会重新创建 checkboxHandler,这会破坏 puprose。幸运的是,尽管 checkboxHandler 不知道从哪里获取当前状态,但 React 提供了一种通过设置状态回调访问它的方法。如果您将逻辑从checkboxHandler 移动到回调,则checkboxHandler 无需捕获selectedChecks。所以我们从

  1. checkBoxHandler 根据在​​ checkBoxHandler 创建时捕获的 selectedChecks 计算新状态
  2. checkBoxHandler 将新状态传递给 React

  1. checkBoxHandler 将方法传递给 React
  2. React 将当前状态传递给该方法
  3. 该方法根据新状态计算新状态并将其传递给 React

【讨论】:

  • 娜迪亚谢谢!这解决了性能问题,但是现在模式没有用我的状态的 selectecChecks 更新。我只是将我的新 useCallback 函数添加到帖子中,对吗?
  • 啊,我明白了,这是因为你修改了状态,但是状态是在 useCallback 中捕获的,你可以稍微改变你的代码,这样你就可以更新 selectedChecks 而无需重新定义回调,但前提是 checks 相当稳定(它们来自哪里?)
  • 在我的 useEffect 中我调用了一个 API,在那里我用响应填充了我的检查。
  • Nadia,试试吧,现在每个选择都会在空数组和未定义的数组之间连续交替。
  • 你有没有机会在 codepen 或类似网站上做一个这样的例子?
猜你喜欢
  • 2020-05-04
  • 2022-06-29
  • 1970-01-01
  • 2021-03-05
  • 1970-01-01
  • 2015-09-08
  • 1970-01-01
  • 1970-01-01
  • 2019-11-24
相关资源
最近更新 更多