为什么即使有键也要重新渲染所有子组件?

时间:2020-06-08 08:06:04

标签: javascript reactjs performance

我当时正在玩react-dev-tools chrome扩展程序,发现我所有的组件都在重新渲染。

App.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

/* 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

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;

当我使用探查器检查删除单个项目后为什么所有“颜色”组件都已重新渲染时,它抱怨handleColorClick属性已更改。我将deleteItem更改为handleColorClick,这不是一个箭头函数,但是结果是相同的。我还传递了唯一的ID。有趣的是,当我通过const key = Math.random()而不是const key = color.id时,我的颜色分量没有重新渲染。因此,它与按键有关。我想了解为什么在传递唯一ID作为键时为什么重新渲染组件。

2 个答案:

答案 0 :(得分:3)

阻止React功能组件重新呈现的唯一方法是使用React.memo来记住该组件。此处的备注表示,如果组件的prop不变-使用===运算符彼此严格相等,则组件的最后一个渲染输出将被重新使用,而不是重新渲染整个组件。

但是,当您谈论作为对象或函数的prop时,React.memo本身会很棘手-严格的===比较会检查引用相等性的值。这意味着对于deleteItem之类的函数,需要使用React.useCallback之类的东西来记忆引用本身,以使它们自己在渲染之间不会改变,这将使React.memo跳闸并导致重新渲染在直觉上似乎不应该的情况。

如您所见,当您尝试跟踪功能,对象,组件等的记忆时,它很快变得非常复杂。

真的,有什么意义?

从备忘录中获得的性能提升(即使它们实现了)微乎其微。这是过早优化的经典案例,有时称为“ root of all evil”,因为它是不必要的时间浪费,几乎没有收获,而且增加了复杂性。

在优化的生产环境中进行响应本身的速度非常快,擅长解决差异问题,并且在大多数情况下,每秒可以将整个应用重新渲染数十次,而不会出现任何明显的速度下降。当您需要对性能产生实际,可衡量的影响时,才应该开始使用备忘录等功能来优化应用。

简而言之,您不必担心“不必要的”退货。

我会再强调一次:

请不要担心“不必要的”游戏。

严重。

PS:之所以对key使用随机值,似乎消除了不必要的重新渲染,是因为每次组件渲染实际上是该组件的全新实例,而不是重新渲染相同的组件。 React在引擎盖下使用key道具来跟踪渲染之间的哪个组件。如果该值不可靠,则意味着React每次都会在字面上渲染NEW组件。您基本上是在破坏所有旧组件,并从头开始重新创建它们,尽管使用相同的道具或其他任何东西,但是请不要误会,它们在渲染之间不是相同的组件。 (即使包括钩子在内的内部状态也将被删除)

答案 1 :(得分:1)

根据您所说的handleColorClick道具已更改,这就是为什么要重新渲染组件的原因。由于您使用的是功能组件,并且在组件中具有钩子,因此当重新渲染组件时,功能handleColorClick会重新定义,并且引用也会更改。这就是即使您将唯一的id作为键传递,也要重新渲染组件的原因。

为避免使用useCallback钩子,除非提供给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;