简体   繁体   English

避免组件在 state 用钩子更改时重新渲染

[英]Avoid component to rerender at state change with hooks

I have a Layout component, which has a Table component, with a component Entry for each row.我有一个 Layout 组件,它有一个 Table 组件,每行都有一个组件 Entry。

Each row can be selected, so later with a button I can send all the entries to a REST service, so each time a row is selected I add it to my state.可以选择每一行,因此稍后我可以使用按钮将所有条目发送到 REST 服务,因此每次选择一行时,我都会将其添加到我的 state。

But each time the state changes, my Layout component renders, rendering each entry of the table, that makes me lost lots of performance and time.但是每次 state 更改时,我的 Layout 组件都会渲染,渲染表格的每个条目,这让我失去了很多性能和时间。

Is there a way to avoid rerendering the component?有没有办法避免重新渲染组件? I'm trying to avoid using class components.我试图避免使用 class 组件。

This function triggers the rendering...这个 function 触发渲染...

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

}

Why am I using the selected checks as a state?为什么我将选定的支票用作 state? Because the rest service's button only renders when there are 1 or more selected checks.因为 rest 服务的按钮仅在有 1 个或多个选中检查时呈现。


Mi layout component which is rerendering...正在重新渲染的 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>
    )

My table component;我的表格组件;

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

My entry component;我的入口组件;

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

EDIT after Nadia's comment:在 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);
    }

}, []);

If you wrap RACheckEntry in React.memo ( RACheckEntry =React.memo((props) => {..}) ) React will only re-render it when props change, however one of you props is a method checkboxHandler , I don't see where you define it, but if it is defined inside a functional component, it'll be re-created on each render, making memo useless.如果您将RACheckEntry包装在React.memo中( RACheckEntry =React.memo((props) => {..}) )React 只会在道具更改时重新渲染它,但是其中一个道具是方法checkboxHandler ,我不看不到你在哪里定义它,但如果它是在功能组件中定义的,它将在每次渲染时重新创建,使memo毫无用处。 To avoid this problem React provides useCallback hook, if you define your handler with it it'll stay the same between renders ( const checkboxHandler= useCallback(() => {...},[] ).为了避免这个问题,React 提供了useCallback钩子,如果你用它定义你的处理程序,它将在渲染之间保持不变( const checkboxHandler= useCallback(() => {...},[] )。

Someone had a similar problem with a different table and it seems it work for them react-table is extremely slow with react-select: how to speed it up?有人对不同的表有类似的问题,似乎对他们有用 react-table 对 react-select 非常慢:如何加快速度?

Update: move all manipulations with state inside setSelectedChecks callback, so you don't depend on the current state inside checkBoxHandler更新:在 setSelectedChecks 回调中使用 state 移动所有操作,因此您不依赖于 checkBoxHandler 中的当前 state

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

here's a simplified version of it https://codesandbox.io/s/trusting-franklin-euzn7?file=/src/App.js这是它的简化版本https://codesandbox.io/s/trusting-franklin-euzn7?file=/src/App.js

Update 2:更新 2:

The reason why it didn't work initially is a combination of two factors: JavaScript closure and React immutable state.它最初不起作用的原因是两个因素的组合:JavaScript 关闭和 React 不可变 state。 When a JavaScript function is created it is enclosed with the surrounding state (basically the viable names are replaced with the actual addresses in memory).当创建 JavaScript function 时,它被周围的 state 包围(基本上可行的名称被替换为内存中的实际地址)。 But then, when you set state in React, you don't modify existing objects in memory, you create brand new ones.但是,当您在 React 中设置 state 时,您不会修改 memory 中的现有对象,而是创建全新的对象。 This means that checkboxHandler created in the first render is stuck with two empty arrays, with no way to know where current state is in memory.这意味着在第一次渲染中创建的checkboxHandler被两个空的 arrays 卡住了,无法知道当前 state 在 memory 中的位置。

To solve this problem you can either pass checks and selectedChecks as parameters to checkboxHandler or add them as dependencies to useCallback .要解决此问题,您可以将checksselectedChecks作为参数传递给checkboxHandler或将它们作为依赖项添加到useCallback In the later case React with re-create checkboxHandler each time one of the dependencies changed.在后一种情况下,每次依赖项之一发生更改时,都使用重新创建checkboxHandler进行反应。 Now, checks are fine as they are only updated once and this will result in updating all entries anyways.现在, checks很好,因为它们只更新一次,这将导致更新所有条目。 But if you add selectedChecks as a dependency, checkboxHandler will be recreated each time selectedChecks are updated, which defeats the puprose.但是,如果您将selectedChecks添加为依赖项,则checkboxHandler将在每次selectedChecks更新时重新创建,这会破坏 puprose。 Luckily although checkboxHandler has no idea where to get current state, React provides a way to access it via set state callback.幸运的是,虽然checkboxHandler不知道从哪里获取当前的 state,但 React 提供了一种通过设置 state 回调来访问它的方法。 If you move the logic from checkboxHandler to the callback there is no need for checkboxHandler to capture selectedChecks .如果将逻辑从checkboxHandler移动到回调,则checkboxHandler不需要捕获selectedChecks So we are moving from所以我们从

  1. checkBoxHandler calculates new state based on selectedChecks captured at the moment of checkBoxHandler creation checkBoxHandler根据创建checkBoxHandler时捕获的selectedChecks计算新的 state
  2. checkBoxHandler passes new state to React checkBoxHandler将新的 state 传递给 React

to

  1. checkBoxHandler passes a method to React checkBoxHandler将方法传递给 React
  2. React passes current state to that method React 将当前 state 传递给该方法
  3. the method calculates new state based on the fresh state and passes it to React该方法基于新的 state 计算新的 state 并将其传递给 React

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM