最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React is rerendering my list even though each child in array has its unique key - Stack Overflow

programmeradmin5浏览0评论

So, as far as I understand react only rerenders new elements with new keys. Thats not working for me though. I have a list of posts, that are limited to 3. When the user scrolls to bottom of page I add 3 to the limit, which means at the bottom of the page 3 older posts are supposed to be shown. What I have now works, but the entire list is being rerendered. And it jumps to the top which is also not wanted (this I can fix though, main problem is the rerendering). They all have unique keys. How can I prevent this behaviour?

thisGetsCalledWhenANewPostComesIn(newPost){
    let newPosts = _.clone(this.state.posts);
    newPosts.push(newPost);
    newPosts.sort((a,b) => b.time_posted - a.time_posted); 
    this.setState({posts: newPosts});
}

render(){
     return (
         <div ref={ref => {this.timelineRef = ref;}} style={styles.container}>
             {this.state.posts.map(post =>
                 <Post key={post.id} post={post} />
             )}
         </div>
     );
}

So, as far as I understand react only rerenders new elements with new keys. Thats not working for me though. I have a list of posts, that are limited to 3. When the user scrolls to bottom of page I add 3 to the limit, which means at the bottom of the page 3 older posts are supposed to be shown. What I have now works, but the entire list is being rerendered. And it jumps to the top which is also not wanted (this I can fix though, main problem is the rerendering). They all have unique keys. How can I prevent this behaviour?

thisGetsCalledWhenANewPostComesIn(newPost){
    let newPosts = _.clone(this.state.posts);
    newPosts.push(newPost);
    newPosts.sort((a,b) => b.time_posted - a.time_posted); 
    this.setState({posts: newPosts});
}

render(){
     return (
         <div ref={ref => {this.timelineRef = ref;}} style={styles.container}>
             {this.state.posts.map(post =>
                 <Post key={post.id} post={post} />
             )}
         </div>
     );
}
Share Improve this question edited Dec 16, 2016 at 14:48 ArneHugo 6,5392 gold badges28 silver badges51 bronze badges asked Dec 16, 2016 at 13:54 ThatBrianDudeThatBrianDude 3,2003 gold badges22 silver badges45 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 7

Having unique keys alone does not prevent rerendering ponents that have not changed. Unless you extend PureComponent or implement shouldComponentUpdate for the ponents, React will have to render() the ponent and pare it to the last result.

So why do we need keys when it's really about shouldComponentUpdate?

The purpose of giving each ponent in a list a unique key is to pass the props to the "right" ponent instances, so that they can correctly pare new and old props.

Imagine we have a list of items, e.g.:

  • A -> ponentInstanceA
  • B -> ponentInstanceB
  • C -> ponentInstanceC

After applying a filter, the list must be rerendered to show the new list of ponents, e.g.:

  • C -> ?

Without proper unique keys, the ponent that previously rendered A will now receive the prop(s) for C. Even if C is unchanged, the ponent will have to rerender as it received pletely different data:

  • C -> ponentInstanceA // OH NO!

With proper unique keys, the ponent that rendered C will receive C again. shouldComponentUpdate will then be able to recogize that the render() output will be the same, and the ponent will not have to rerender:

  • C -> ponentInstanceC

If your list of items take a long time to render, e.g. if it's a long list or each element is a plex set of data, then you will benefit from preventing unnecessary rerendering.

Personal anecdote

In a project with a list of 100s of items which each produced 1000s of DOM elements, changing from

list.map((item, index) => <SomeComp key={index} ... />)

to

list.map(item => <SomeComp key={item.id} ... />)

reduced the rendering time by several seconds. Never use array index as key.

You will have to implement shouldComponentUpdate(nextProps, nextState) in the Post ponent. Consider extending the PureComponent class for the Post ponent instead of the default React Component.

Good luck!


PS: you can use a string as ref parameter for your div in the render method like so:

render() {
  return (
    <div 
      ref='myRef'
      style={styles.container}
    >
      {this.getPostViews()}
    </div>
  );
}

Then, if you want to refer to this element, use it like this.refs.myRef. Anyway, this is just a personal preference.

Okay, my bad. I thought I'd only post the "relevant" code, however it turns out, the problem was in the code I left out:

    this.setState({posts: []}, ()=> {
        this.postListenerRef = pletedPostsRef.orderByChild('time')
        .startAt(newProps.filter.fromDate.getTime())
        .endAt(newProps.filter.toDate.getTime())
        .limitToLast(this.props.filter.postCount)
        .on('child_added', snap => {
            Database.fetchPostFromKey(snap.key)
            .then(post => {             
                let newPosts = _.clone(this.state.posts);
                newPosts.push(_.assign(post, {id: snap.key}));
                newPosts.sort((a,b) => b.time_posted - a.time_posted); 
                this.setState({posts: newPosts});
            }).catch(err => {throw err;});
        });
    }); 

I call setState({posts: []}) which I am 99% sure is the problem.

发布评论

评论列表(0)

  1. 暂无评论