【问题标题】:In React, how to avoid unnecessary rerender when array used for rendering changes?在 React 中,当用于渲染的数组发生变化时,如何避免不必要的重新渲染?
【发布时间】:2021-06-13 03:13:25
【问题描述】:

similar question 中,解决方案是在遍历数组以呈现组件时使用唯一值而不是映射索引作为“键”道具。但是,它对我不起作用,因为在我从数组中删除一个元素后,remaning 组件仍然会重新呈现。我在这里错过了什么?

这是我的尝试,但没有按预期工作:

App.js

import { useState } from "react";
import Column from "./Column.js";

export default function App() {
  const data = [
    {
      name: "lebron",
      age: 36,
      team: "lakers"
    },
    {
      name: "chris",
      age: 36,
      team: "suns"
    },
    {
      name: "james",
      age: 32,
      team: "nets"
    }
  ];
  const [players, setPlayers] = useState(data);
  const handleClick = () => {
    return function () {
      const p = [...players];
      p.splice(0, 1);
      setPlayers(p);
    };
  };
  return (
    <>
      <table>
        <tr>
          <td>name</td>
          <td>age</td>
          <td>team</td>
        </tr>
        {players.map((p) => (
          <Column key={p.name} player={p} onClick={handleClick(p.name)} />
        ))}
      </table>
      <button onClick={handleClick()}>Delete</button>
    </>
  );
}

Column.js:

import React from "react";

export default function Column({ player, onClick }) {
  console.log(player.name, "update", Date());
  return (
      <tr>
        <td>{player.name}</td>
        <td>{player.age}</td>
        <td>{player.team}</td>
      </tr>
  );
}

【问题讨论】:

  • 您正在更改组件的状态,这会导致重新渲染。我要说的唯一想法是将您的数据数组移到组件之外,这样就不会在每次重新渲染时都定义它
  • 你需要记住 Column 组件。 es.reactjs.org/docs/react-api.html 但你需要有一些反应性能背景。
  • 不得不说你的handleClick功能不对。当您使用 splice 时,您正在修改播放器数组,并且在 React 状态下只能使用调度函数 (setPlayers) 进行修改。在您的情况下,您应该使用 .filter 方法,因为它返回一个新数组。

标签: javascript reactjs


【解决方案1】:

这是默认的 React 行为。

子更新是因为父状态发生了更新。

如果您想重新渲染子组件只有在道具发生变化时,您需要用React.memo HOC 包装您的子组件。

简单地说,React.memoReact 第一次渲染你的组件,记住结果,然后重用上次渲染的结果(跳过不必要的重新渲染)。

React.memo 仅检查 prop 更改。如果你包裹在React.memo 中的函数组件在其实现中有useStateuseReduceruseContext Hook,它仍然会在状态或上下文发生变化时重新渲染。

在你的情况下:

Column.js 文件

import { memo } from "react";

function Column({ player, onClick }) {
  console.log(`Render player ${player.name}`);
  return (
    <tr>
      <td>{player.name}</td>
      <td>{player.age}</td>
      <td>{player.team}</td>
      <button onClick={() => onClick(player)}>Delete</button>
    </tr>
  );
}

export default memo(Column);

App.js 文件

import { useState, useCallback } from "react";
import Column from "./Column";

const data = [
  {
    name: "lebron",
    age: 36,
    team: "lakers"
  },
  {
    name: "chris",
    age: 36,
    team: "suns"
  },
  {
    name: "james",
    age: 32,
    team: "nets"
  }
];

export default function App() {
  const [players, setPlayers] = useState(data);
  const handleClick = useCallback((deleted) => {
    setPlayers((prevState) =>
      prevState.filter((player) => player.name !== deleted.name)
    );
  }, []);

  return (
    <>
      <table>
        <tr>
          <td>name</td>
          <td>age</td>
          <td>team</td>
        </tr>
        {players.map((p) => (
          <Column key={p.name} player={p} onClick={handleClick} />
        ))}
      </table>
      {/* This button makes no sense */}
      <button onClick={() => handleClick(players[0])}>Delete</button>
    </>
  );
}

Code sandbox

我对您的代码做了一些更改:

  • handleClick 功能不对。当您使用splice 时,您正在修改播放器数组,并且在 React 状态下只能使用调度函数 (setPlayers) 修改。在您的情况下,您应该使用 filter 方法,因为它返回一个新数组。

    const handleClick = (deleted) => {
      setPlayers((prevState) =>
        prevState.filter((player) => player.name !== deleted.name)
      );
    };
    
  • 用 React.memo HOC 包装你的 Column 组件。

   export default React.memo(Column);
  • useCallback 包装你的handleClick 函数。这是因为你使用 React.memo 需要记忆你的函数。

    const handleClick = useCallback((deleted) => {
      setPlayers((prevState) =>
        prevState.filter((player) => player.name !== deleted.name)
      );
    }, []);
    

【讨论】:

    【解决方案2】:

    希望这能解决你的问题

    import { useState } from "react";
    function Column({ player, onClick }) {
      console.log(player.name, "update", Date());
      return (
        <tr>
          <td>{player.name}</td>
          <td>{player.age}</td>
          <td>{player.team}</td>
          <td onClick={onClick}>x</td>
        </tr>
      );
    }
    export default function App() {
      const data = [
        {
          name: "lebron",
          age: 36,
          team: "lakers"
        },
        {
          name: "chris",
          age: 36,
          team: "suns"
        },
        {
          name: "james",
          age: 32,
          team: "nets"
        }
      ];
      const [players, setPlayers] = useState(data);
      const handleClick = (index) => {
        return function () {
          if(index===-1)
          return setPlayers([])
          const p = [...players];
          p.splice(index, 1);
          setPlayers(p);
        };
      };
      return (
        <>
          <table>
            <tr>
              <td>name</td>
              <td>age</td>
              <td>team</td>
            </tr>
            {players.map((p, index) => (
              <Column key={p.name} player={p} onClick={handleClick(index)} />
            ))}
          </table>
         <p> Click x to delete</p>
          <button onClick={handleClick(0)}>Delete First</button>
          <button onClick={handleClick(players.length-1)}>Delete Last</button>
          <button onClick={handleClick(-1)}>Delete All</button>
    
        </>
      );
    }
    
    

    https://codesandbox.io/s/objective-sky-77bwz?file=/src/App.js

    【讨论】:

      【解决方案3】:

      我假设你想删除数组的最后一个元素,因为有 pop() 方法

      • 而不是在onClick中直接调用函数
      • 在表格中也使用 thead 和 tbody
        const handleClick = () => {
          const p = [...players];
          p.pop();
          setPlayers(p);
        };
        return (
          <>
            <table>
              <thead>
                <tr>
                  <td>name</td>
                  <td>age</td>
                  <td>team</td>
                </tr>
              </thead>
              <tbody>
                {players.map((p) => (
                  <Column
                    key={p.name}
                    player={p}
                    onClick={() => handleClick(p.name)}
                  />
                ))}
              </tbody>
            </table>
            <button onClick={handleClick}>Delete</button>
          </>
        );
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-25
        • 2023-02-02
        • 2020-01-10
        • 2020-07-26
        • 1970-01-01
        • 2020-12-21
        • 2020-12-13
        • 2021-01-13
        相关资源
        最近更新 更多