React re 在添加新项目时渲染数组中的所有项目,尽管所有项目都使用唯一键

Posted

技术标签:

【中文标题】React re 在添加新项目时渲染数组中的所有项目,尽管所有项目都使用唯一键【英文标题】:React re renders all items in array when new items are added despite using unique keys for all items 【发布时间】:2019-03-01 17:24:33 【问题描述】:

原帖

我有一个页面,它使用 Array.prototype.map 呈现包含在 redux 状态中的数组中的每个项目。该数组最初在调用 componentWillMount() 回调时填充,然后在用户请求更多数据时附加,即当用户滚动到页面底部并且有更多项目要获取时。我确保当使用 Array.prototype.concat() 附加数组时,redux 状态不会发生变化。然而,当更多的项目被添加到数组中时,看起来 react re 渲染了数组中的每个项目,而不仅仅是新项目,尽管为每个项目设置了一个唯一的键。我渲染组件的源代码如下:

this.props.state.list.map((item, index) => 
  return (
    <Grid className=classes.column key=item._id item xs=6 sm=6 md=4>
      <Card className=classes.card>
        <CardActionArea className=classes.cardAction>
          <div className=classes.cardWrapper>
            <div className=classes.outer>
              <div className=classes.bgWrapper>
                <img
                  className=[classes.bg, "lazyload"].join(" ")
                  data-sizes="auto"
                  data-src=genSrc(item.pictures[0].file)
                  data-srcset=genSrcSet(item.pictures[0].file)
                  alt=item.name
                />
              </div>
            </div>
            <CardContent className=classes.cardContent>
              <div className=classes.textWrapper>
                <div className=classes.textContainer>
                  <Typography
                    gutterBottom
                    className=[classes.text, classes.title].join(" ")
                    variant="title"
                    color="inherit"
                  >
                    item.name
                  </Typography>
                  <Typography
                    className=[classes.text, classes.price].join(" ")
                    variant="subheading"
                    color="inherit"
                  >
                    item.minPrice === item.maxPrice
                      ? `$$item.minPrice.toString()`
                      : `$$item.minPrice
                          .toString()
                          .concat(" - $", item.maxPrice.toString())`
                  </Typography>
                </div>
              </div>
            </CardContent>
          </div>
        </CardActionArea>
      </Card>
    </Grid>
  );
);

这是我的减速器,如果你认为它可能与此有关:

import 
  FETCH_PRODUCTS_PENDING,
  FETCH_PRODUCTS_SUCCESS,
  FETCH_PRODUCTS_FAILURE
 from "../actions/products";

let defaultState = 
  list: [],
  cursor: null,
  calls: 0,
  status: null,
  errors: []
;

const reducer = (state = defaultState, action) => 
  switch (action.type) 
    case FETCH_PRODUCTS_PENDING:
      return 
        ...state,
        status: "PENDING"
      ;
    case FETCH_PRODUCTS_SUCCESS:
      return 
        ...state,
        calls: state.calls + 1,
        cursor: action.payload.data.cursor ? action.payload.data.cursor : null,
        list: action.payload.data.results
          ? state.list.concat(action.payload.data.results)
          : state.list,
        status: "READY"
      ;
    case FETCH_PRODUCTS_FAILURE:
      return 
        ...state,
        status: "FAIL",
        call: state.calls + 1,
        errors: [...state.errors, action.payload]
      ;
    default:
      return state;
  
;

export default reducer;

我从类似于我的问题的答案中读到,为从数组生成的每个组件设置一个 shouldComponentUpdate() 方法可能会起到作用,但没有明确的例子说明如何做到这一点。我非常感谢您的帮助。谢谢!

编辑 1

我还应该指定当新项目添加到数组中时,从数组呈现的组件完全卸载并重新安装在浏览器 DOM 中。理想情况下,我希望浏览器永远不要卸载现有组件,而只附加新渲染的组件。

编辑 2

这是呈现列表的组件的完整代码。

class Products extends React.Component 

  componentDidMount() 
    const  fetchNext, fetchStart, filter  = this.props
    fetchStart(filter)

    window.onscroll = () => 
      const  errors, cursor, status  = this.props.state
      if (errors.length > 0 || status === 'PENDING' || cursor === null) return
      if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) 
        fetchNext(filter, cursor)
      
    
  

  render() 
    const  classes, title  = this.props

    return (
      <div className=classes.container>
        <Grid container spacing=0>
          
            this.props.state.status && this.props.state.status === 'READY' && this.props.state.list &&
            this.props.state.list.map((item, index) => 
              return (
                <Grid className=classes.column key=item._id item xs=6 sm=6 md=4>
                  <Card className=classes.card>
                    <CardActionArea className=classes.cardAction>
                      <div className=classes.cardWrapper>
                        <div className=classes.outer>
                          <div className=classes.bgWrapper>
                            <img className=[classes.bg, 'lazyload'].join(' ') data-sizes='auto' data-src=genSrc(item.pictures[0].file) data-srcset=genSrcSet(item.pictures[0].file) alt=item.name />
                          </div>
                        </div>
                        <CardContent className=classes.cardContent>
                          <div className=classes.textWrapper>
                            <div className=classes.textContainer>
                              <Typography gutterBottom className=[classes.text, classes.title].join(' ') variant="title" color='inherit'>
                                item.name
                              </Typography>
                              <Typography className=[classes.text, classes.price].join(' ') variant='subheading' color='inherit'>
                                item.minPrice === item.maxPrice ? `$$item.minPrice.toString()` : `$$item.minPrice.toString().concat(' - $', item.maxPrice.toString())`
                              </Typography>
                            </div>
                          </div>
                        </CardContent>
                      </div>
                    </CardActionArea>
                  </Card>
                </Grid>
              )
            )
          
        </Grid>
      </div>
    )
  

【问题讨论】:

如果状态改变,一个组件和它的所有子组件都会重新渲染。这太正常了。但是,渲染并不意味着这些组件将再次卸载/安装或销毁/重新创建。这只会发生变化的部分。这与reconciliation 有关。所以,渲染并没有那么昂贵,DOM 操作才是。但是,如果您确定渲染所有这些组件会导致一些性能损失,那么您可以检查shouldComponentUpdate(小心,有时触摸它会导致性能下降)或者您可以使用 PureComponent。 如果是这种情况,那么我应该了解更多信息:) 我认为在这种情况下只会发生重新渲染,而不是所有的 DOM 操作。我知道 React 会添加或删除子项,但我不认为在这种情况下所有 DOM 都会发生变化。 这也是我所期望的。我很确定我做错了什么。无论如何,再次感谢您的 cmets 当你说消失和重新出现时,你的意思是加载时从可见到消失吗?您是否对减速器上的状态字段做了任何会导致列表项不显示的事情?发布整个组件代码而不仅仅是列表渲染可能会有所帮助。 您可以写一个pure Component 来渲染您的地图项并将这些项作为道具传递给该组件,看看是否发生了同样的事情。更多参考reactjs.org/docs/optimizing-performance.html 【参考方案1】:

所以事实证明这是一个条件渲染导致渲染列表消失。在我的例子中,我设置它以便组件仅在所有正在进行的 fetch 操作都已完成时才呈现列表,因此列表当然会在 fetch 调用之间消失。

更改以下内容

    
      this.props.state.status && this.props.state.status === 'READY' && this.props.state.list &&
        this.props.state.list.map((item, index) => 
          return <Item />
        
    

为此,取消对已完成获取状态的检查解决了我的问题。

    
      this.props.state.list &&
        this.props.state.list.map((item, index) => 
          return <Item />
        
    

【讨论】:

以上是关于React re 在添加新项目时渲染数组中的所有项目,尽管所有项目都使用唯一键的主要内容,如果未能解决你的问题,请参考以下文章

React Native FlatList 不渲染数组中的项目

React使用hooks-useMemo

React:给定一个数组,以相反的顺序有效地渲染元素

React-Spring useTransition 将我的列表项打乱

无法删除数组项,错误的项总是被删除

重新渲染 Reactjs 数组组件