避免使用钩子在状态改变时重新渲染组件

时间:2021-03-25 19:15:00

标签: javascript reactjs react-hooks material-ui

我有一个 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);
    }

}

为什么我使用选中的支票作为状态?因为其余服务的按钮仅在有 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>
          
    )

在纳迪亚评论后编辑:

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

}, []);

1 个答案:

答案 0 :(得分:1)

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

有人在不同的表上遇到了类似的问题,似乎对他们有用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 添加为依赖项,每次更新 checkboxHandler 时都会重新创建 selectedChecks,这会破坏目的。幸运的是,虽然 checkboxHandler 不知道从哪里获取当前状态,但 React 提供了一种通过设置状态回调访问它的方法。如果您将逻辑从 checkboxHandler 移至回调,则 checkboxHandler 无需捕获 selectedChecks。所以我们要从

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

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