如何在React Native中更新FlatList中的单个项目?

K.W*_*.Wu 19 react-native

注意:我在那里发布了一个答案,我个人认为这是迄今为止最好的解决方案.虽然它不是最高级别的答案,但根据我得到的结果,它非常有效.

--------------------------------------------- 原始问题 --- -------------------------------------------------- -

假设我正在写一个Twitter克隆,但更简单.我将每个项目放在FlatList中并渲染它们.

为了"喜欢"一个帖子,我按下帖子上的"喜欢"按钮,"喜欢"按钮变成红色,我再次按下它,它变成灰色.

这就是我到目前为止:我存储了所有加载的帖子this.state,每个帖子都有一个名为"likes"的属性,它是boolean,表示该用户是否喜欢这个帖子,当用户按下"喜欢"时,我去了以state.posts和更新liked该职位的性质,然后利用this.setState更新的帖子,像这样:

// 1. FlatList
<FlatList
    ...
    data={this.state.posts}
    renderItem={this.renderPost}
    ...
/> 

// 2. renderPost
renderPost({ item, index }) {
    return (
        <View style={someStyle}>
            ... // display other properties of the post
            // Then display the "like" button
            <Icon
                name='favorite'
                size={25}
                color={item.liked ? 'red' : 'gray'}
                containerStyle={someStyle}
                iconStyle={someStyle}
                onPress={() => this.onLikePost({ item, index })}
            />
            ...
        </View>
    );
}

// 3. onLikePost
likePost({ item, index }) {
    let { posts } = this.state;
    let targetPost = posts[index];

    // Flip the 'liked' property of the targetPost
    targetPost.liked = !targetPost.liked;

    // Then update targetPost in 'posts'
    posts[index] = targetPost;

    // Then reset the 'state.posts' property
    this.setState({ posts });
}
Run Code Online (Sandbox Code Playgroud)

然而,这种方法很有效.当我按下它时,"喜欢"按钮的颜色翻转,但通常需要大约1秒钟才能改变颜色.我想要的是当我按下它时,颜色几乎会同时翻转.

我知道为什么会发生这种情况,我可能不会使用this.setState,因为当我这样做时,posts状态发生了变化,所有的帖子都被重新渲染,但我还能尝试其他方法吗?

tom*_*wel 13

您可以设定extraDataFlatList:

<FlatList
...
    extraData={this.state}
    data={this.state.posts}
    renderItem={this.renderPost}
    ...
/> 
Run Code Online (Sandbox Code Playgroud)

state.posts或者state.posts项目改变时,FlatList将重新渲染.

来自FlatList#extradata:

用于告知列表重新呈现的标记属性(因为它实现了PureComponent).如果你的任何renderItem,Header,Footer等函数依赖于数据道具之外的任何东西,请将其粘贴在此处并进行不可变处理.

  • 这将重新呈现列表中的所有项目。我认为问题是如何更改列表中的单个项目。任何人都可以提供完整的工作示例和代码来演示单个项目的更改吗? (2认同)

Shu*_*ngh 8

如果您正在测试Android而不是尝试关闭开发人员模式.或者您是否正在使用某些API并更新服务器上的帖子并更新与服务器响应相对应的UI中的like按钮?如果是这种情况请告诉我,我也遇到了这个并且我解决了它.另外,我已经在代码中注释了第二行,这是不需要的.

// 1. FlatList
<FlatList
    ...
    data={this.state.posts}
    renderItem={this.renderPost}
    ...
/> 

// 2. renderPost
renderPost({ item, index }) {
    return (
        <View style={someStyle}>
            ... // display other properties of the post
            // Then display the "like" button
            <Icon
                name='favorite'
                size={25}
                color={item.liked ? 'red' : 'gray'}
                containerStyle={someStyle}
                iconStyle={someStyle}
                onPress={() => this.onLikePost({ item, index })}
            />
            ...
        </View>
    );
}

// 3. onLikePost
likePost({ item, index }) {
    let { posts } = this.state;
    let targetPost = posts[index];

    // Flip the 'liked' property of the targetPost
    targetPost.liked = !targetPost.liked;

    // Then update targetPost in 'posts'
    // You probably don't need the following line.
    // posts[index] = targetPost;

    // Then reset the 'state.posts' property
    this.setState({ posts });
}
Run Code Online (Sandbox Code Playgroud)


K.W*_*.Wu 7

不要误会我的意思,@ ShubhnikSingh的回答确实有帮助,但是我撤回了它,因为很久以前我找到了解决该问题的更好方法,最后我记得将其发布在这里。

假设我的帖子包含以下属性:

{
    postId: "-L84e-aHwBedm1FHhcqv",
    date: 1525566855,
    message: "My Post",
    uid: "52YgRFw4jWhYL5ulK11slBv7e583",
    liked: false,
    likeCount: 0,
    commentCount: 0
}
Run Code Online (Sandbox Code Playgroud)

Where liked表示查看此帖子的用户是否喜欢此帖子,这将确定“赞”按钮的颜色(默认情况下为灰色,如果为,则为红色liked == true)。


以下是重新创建我的解决方案的步骤:进行“发布”,Component并在中进行呈现FlatListPureComponent如果您没有传递给您的任何道具,Post例如可以看成不浅的数组或对象,则可以使用React 。如果您不知道这意味着什么,请按照下面的步骤使用常规Component和覆盖shouldComponentUpdate

class Post extends Component {                                                      
  // This determines whether a rendered post should get updated                     
  // Look at the states here, what could be changing as time goes by?               
  // Only 2 properties: "liked" and "likeCount", if the person seeing               
  // this post ever presses the "like" button                                       
  // This assumes that, unlike Twitter, updates do not come from other              
  // instances of the application in real time.                                     
  shouldComponentUpdate(nextProps, nextState) {                                     
    const { liked, likeCount } = nextProps                                          
    const { liked: oldLiked, likeCount: oldLikeCount } = this.props                 

    // If "liked" or "likeCount" is different, then update                          
    return liked !== oldLiked || likeCount !== oldLikeCount                         
  }                                                                                 

  render() {                                                                        
    return (                                                                        
      <View>                                                                        
        {/* ...render other properties */}                                          
        <TouchableOpacity                                                           
          onPress={() => this.props.onPressLike(this.props.postId)}                 
        >                                                                           
          <Icon name="heart" color={this.props.liked ? 'gray' : 'red'} />           
        </TouchableOpacity>                                                         
      </View>                                                                       
    )                                                                               
  }                                                                                 
}                                                                                   
Run Code Online (Sandbox Code Playgroud)

然后,创建一个PostList组件,该组件将负责处理加载帖子的逻辑和类似交互的处理:

class PostList extends Component {                                                        

/**                                                                                       
 * As you can see, we are not storing "posts" as an array. Instead,                       
 * we make it a JSON object. This allows us to access a post more concisely               
 * than if we stores posts as an array. For example:                                      
 *                                                                                        
 * this.state.posts as an array                                                           
 * findPost(postId) {                                                                     
 *   return this.state.posts.find(post => post.id === postId)                             
 * }                                                                                      
 * findPost(postId) {                                                                     
 *   return this.state.posts[postId]                                                      
 * }                                                                                      
 * a specific post by its "postId", you won't have to iterate                             
 * through the whole array, you can just call "posts[postId]"                             
 * to access it immediately:                                                              
 * "posts": {                                                                             
 *     "<post_id_1>": { "message": "", "uid": "", ... },                                  
 *     "<post_id_2>": { "message": "", "uid": "", ... },                                  
 *     "<post_id_3>": { "message": "", "uid": "", ... }                                   
 * }                                                                                      
 * FlatList wants an array for its data property rather than an object,                   
 * so we need to pass data={Object.values(this.state.posts)} rather than                  
 * just data={this.state.posts} as one might expect.                                      
*/                                                                                        

  state = {                                                                                 
    posts: {}                                                                               
    // Other states                                                                         
  }                                                                                         

  renderItem = ({ item }) => {
    const { date, message, uid, postId, other, props, here } = item
    return (
      <Post
        date={date}
        message={message}
        uid={uid}
        onPressLike={this.handleLikePost}
      />
    )
  }

  handleLikePost = postId => {
    let post = this.state.posts[postId]
    const { liked, likeCount } = post

    const newPost = {
      ...post,
      liked: !liked,
      likeCount: liked ? likeCount - 1 : likeCount + 1
    }

    this.setState({
      posts: {
        ...this.state.posts,
        [postId]: newPost
      }
    })
  }

  render() {
    return (
      <View style={{ flex: 1 }}>
        <FlatList
          data={Object.values(this.state.posts)}
          renderItem={this.renderItem}
          keyExtractor={({ item }) => item.postId}
        />
      </View>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

综上所述:

1)编写一个自定义组件(Post),以呈现“ FlatList”中的每个项目

2)覆盖自定义组件(Post)函数的“ shouldComponentUpdate”,以告知组件何时更新

处理父组件(PostList)中的“喜欢状态” ,并将数据向下传递给每个子组件

  • 这个技巧对我不起作用,你能帮助我如何找到一个好的解决方案,因为当我更新一个平面列表的单个项目时,所有项目都会重新渲染,如果我在那个时候使用你的解决方案,什么都不会更新 (2认同)