简体   繁体   English

尽管有键,为什么我的所有子组件都重新渲染?

[英]Why are all my child components re-rendering despite having keys?

I was playing around with react-dev-tools chrome extension and found out that all my components are re-rendering.我在玩 react-dev-tools chrome 扩展,发现我所有的组件都在重新渲染。

App.js应用程序.js

import React from 'react';
import './App.css';
import Header from './components/molecules/Header/Header';
// import { colorListGenerator } from './core-utils/helpers';
import ColorPalette from './components/organisms/ColorPalette/ColorPalette';

export const colorListGenerator = (n) => {
  let colorArray = []
  for(let i=0; i<n; i++) {
      let randomColor = '#'+Math.floor(Math.random()*16777215).toString(16);
      let id="id" + Math.random().toString(16).slice(2)
      console.log(typeof(id), id)
      let color = {
          id: id,
          hex: randomColor
      }
      colorArray.push(color);
  }
  return colorArray
}

const App = () => {
  const colors=colorListGenerator(10);

  return (
    <div className="App">
      <Header/>
      <ColorPalette colorPalette={colors} />
    </div>
  );
}

export default App;

ColorPalette.js调色板.js

/* eslint-disable eqeqeq */
import React from 'react';
import Color from '../../atoms/Color';
import './ColorPalette.css';

const ColorPalette = ({ colorPalette }) => {

    const [colors, setColors] = React.useState(colorPalette);

    // const handleColorClick = (event) => {
    //     const id = event.currentTarget.getAttribute('id')
    //     const index = colors.findIndex(item => item.id == id);
    //     setColors(colors.filter(item => item.id != id))
    // }

    const deleteItem = (id) => {
        setColors(colors.filter(item => item.id != id))
    }

    return (
        <div className={'colorPalette'}>
            {colors && colors.map((color, index) => {
                // const key = index
                const key = color.id
                return <Color
                key={key}
                color={color.hex}
                colorKey={key} 
                handleColorClick = {() => {deleteItem(color.id)}}
                /> })}
        </div>
    )
}

// export default React.memo(ColorPalette);
export default ColorPalette;

Color.js颜色.js

import React from 'react';
import './Color.css';
import deleteIcon from '../../../delete-icon.png'

const Color = ({ color, colorKey, handleColorClick }) => {
    return (
        <div className="color"
            style={{ backgroundColor: color }}
            // no need to mention key here
            // key={colorKey}
            id={colorKey}
            onClick={handleColorClick} >
            <p> {colorKey} </p>
            <img src={deleteIcon}
                alt={'delete'}
                className="delete"
            />
        </div>
    )
}

// export default React.memo(Color);
export default Color;

When I use the profiler to check why all my 'Color' components have re-rendered after deleting a single item, it complains that handleColorClick prop has changed.当我使用分析器检查为什么在删除单个项目后我的所有“颜色”组件都重新渲染时,它抱怨 handleColorClick 道具已更改。 I changed the deleteItem to handleColorClick which isn't an arrow function, but the result is the same.我将deleteItem更改为handleColorClick,它不是箭头function,但结果是一样的。 I'm also passing unique ids.我还传递了唯一的 ID。 Interestingly, when I pass const key = Math.random() instead of const key = color.id my Color components are not rerendering.有趣的是,当我通过const key = Math.random()而不是const key = color.id ,我的 Color 组件不会重新渲染。 So it has something to do with the keys.所以它与键有关。 I want to understand why my components are rerendering when I pass unique ids as keys.我想了解为什么当我将唯一 ID 作为键传递时我的组件会重新渲染。

The only way a React functional component will be prevented from rerendering is by using React.memo to memoize the component.防止 React 功能组件重新渲染的唯一方法是使用React.memo来记忆组件。 Memoization here means that if the component's props do not change - they are strictly equivalent to each other using the === operator - then the component's last render output will be re-used instead of rerendering the entire component.这里的记忆意味着如果组件的 props 没有改变 - 它们使用===运算符严格等价 - 那么组件的最后一次渲染 output 将被重新使用,而不是重新渲染整个组件。

However, React.memo itself gets tricky when you're talking about props that are object or functions - values for which the strict === comparison checks referential equality.但是,当您谈论 object 或函数的道具时, React.memo本身会变得很棘手 - 严格===比较检查引用相等性的值。 That means that for functions like deleteItem need to use something likeReact.useCallback to memoize the references themselves so that they themselves do not change between renders, which will trip up React.memo and lead to rerenders in situations where intuitively it seems like it shouldn't.这意味着对于像deleteItem这样的函数,需要使用像React.useCallback这样的东西来记忆引用本身,这样它们本身就不会在渲染之间发生变化,这将导致React.memo并导致在直觉上看起来应该的情况下重新渲染不。

As you can see, it quickly starts to get quite complicated, as you try to keep track of memoizing your functions, your objects, your components, etc.正如你所看到的,当你试图记录你的函数、对象、组件等时,它很快开始变得相当复杂。

And really, what's the point?真的,有什么意义呢?

The performance gains you get from memoization - if they even materialize - are miniscule.你从记忆中获得的性能提升——如果它们实现的话——是微乎其微的。 This is a classic case of premature optimization, sometimes called the " root of all evil " because of what an unnecessary time sink it is, for little to no gain, and the cost of added complexity.这是过早优化的典型案例,有时被称为“万恶之源”,因为它是一种不必要的时间消耗,几乎没有收获,而且增加了复杂性的代价。

React itself in its optimized production build is insanely fast, good at resolving diffs, and in most cases could rerender your entire app dozens of times per second without any perceivable slowdown. React 本身在其优化的生产构建中非常快,擅长解决差异,并且在大多数情况下,每秒可以重新渲染整个应用程序数十次,而不会出现任何明显的减速。 You should ONLY start optimizing your app with things like memoization when you have ACTUAL, MEASURABLE impacts to performance that you need to address.仅当您对需要解决的性能产生实际的、可衡量的影响时,您才应该开始使用诸如 memoization 之类的东西来优化您的应用程序。

In short, you do not need to worry about "unnecessary" rerenders.简而言之,您无需担心“不必要的”重新渲染。

I'll say it again for emphasis:为了强调,我再说一遍:

DO NOT WORRY ABOUT "UNNECESSARY" RERENDERS.不要担心“不必要的”渲染。

Seriously.严重地。

PS: The reason using a random value for key makes it seem like unnecessary rerenders are eliminated is because every time a component renders it is literally a brand new instance of that component, not the same component being rerendered. PS:使用随机值作为key看起来似乎消除了不必要的重新渲染的原因是因为每次渲染组件时,它实际上都是该组件的全新实例,而不是被重新渲染的相同组件。 React uses the key prop under the hood to track which component is which between renders. React 在底层使用key prop 来跟踪渲染之间的哪个组件。 If that value is unreliable, it means that React is literally rendering NEW components every time.如果该值不可靠,则意味着 React 每次都在渲染新组件。 You're basically destroying all the old components and recreating them from scratch, albeit with the same props or whatever, but make no mistake, they are NOT the same components between renders.您基本上是在销毁所有旧组件并从头开始重新创建它们,尽管使用相同的道具或其他任何东西,但请不要误会,它们在渲染之间不是相同的组件。 (Even their internal state including hooks will be erased) (甚至包括挂钩在内的内部 state 也会被擦除)

As per what you said handleColorClick prop has changed, which is why the components are getting re-rendered.根据您所说, handleColorClick道具已更改,这就是重新渲染组件的原因。 Since you are using functional component and hooks in the component, when the component is getting re-rendered the function handleColorClick is redefined again and the reference is getting changed.由于您在组件中使用功能组件和钩子,因此当组件重新渲染时,function handleColorClick将再次重新定义,并且引用正在更改。 That's the reason why the components are getting re-rendered even though you pass unique ids as keys.这就是为什么即使您将唯一 ID 作为键传递,组件也会重新渲染的原因。

In order to avoid that you can use useCallback hook which will help you not to get a new function reference unless there's a change in the dependencies provided to the useCallback hook为了避免这种情况,您可以使用useCallback挂钩,这将帮助您不获取新的 function 引用,除非提供给useCallback挂钩的依赖项发生变化

/* eslint-disable eqeqeq */
import React, {useCallback} from 'react';
import Color from '../../atoms/Color';
import './ColorPalette.css';

const ColorPalette = ({ colorPalette }) => {

    const [colors, setColors] = React.useState(colorPalette);

    // const handleColorClick = (event) => {
    //     const id = event.currentTarget.getAttribute('id')
    //     const index = colors.findIndex(item => item.id == id);
    //     setColors(colors.filter(item => item.id != id))
    // }

    const deleteItem = useCallback((id) => {
        setColors(colors.filter(item => item.id != id))
    }, [])

    return (
        <div className={'colorPalette'}>
            {colors && colors.map((color, index) => {
                // const key = index
                const key = color.id
                return <Color
                key={key}
                color={color.hex}
                colorKey={key} 
                handleColorClick = {() => {deleteItem(color.id)}}
                /> })}
        </div>
    )
}

// export default React.memo(ColorPalette);
export default ColorPalette;

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

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