【问题标题】:How to update a single item in FlatList in React Native?如何在 React Native 中更新 FlatList 中的单个项目?
【发布时间】:2017-10-28 20:39:20
【问题描述】:

注意:我已经在下面发布了一个答案,我个人认为这是迄今为止最好的解决方案。尽管它不是最高评价的答案,但根据我得到的结果,它非常有效。

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

假设我正在编写一个 Twitter 克隆,但要简单得多。我将每个项目放在 FlatList 中并渲染它们。

要“点赞”帖子,我按帖子上的“点赞”按钮,“点赞”按钮变成红色,我再按一次,它变成灰色。

这就是我目前所拥有的:我将所有加载的帖子存储在this.state,每个帖子都有一个名为“liked”的属性,它是布尔值,当用户按下时表示该用户是否喜欢这个帖子“like”,我去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 });
}

这种方法有效,但是太慢了。当我按下“like”按钮时,它的颜色会翻转,但通常需要大约 1 秒钟才能改变颜色。我想要的是当我按下它时颜色几乎会同时翻转。

我确实知道为什么会发生这种情况,我可能不应该使用this.setState,因为当我这样做时,posts 状态会发生变化,并且所有帖子都会重新呈现,但是我可以尝试其他什么方法?

【问题讨论】:

    标签: react-native


    【解决方案1】:

    你可以在FlatList中设置extraData

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

    state.postsstate.posts的项目发生变化时,FlatList会重新渲染。

    来自FlatList#extradata

    一个标记属性,用于告诉列表重新渲染(因为它实现了 PureComponent)。如果您的任何 renderItem、Header、Footer 等函数依赖于 data 属性之外的任何内容,请将其粘贴在这里并不可变地对待它。

    更新:

    功能组件实现:

    export default function() {
        // list of your data
        const [list, setList] = React.useState([])
        const [extraData, setExtraData] = React.useState(new Date())
    
        // some update on the item of list[idx]
        const someAction = (idx)=>{
            list[idx].show = 1
            setList(list)
            setExtraData(new Date())
        }
        return (
            <FlatList
                // ...
                data={list}
                extraData={extraData}
            />
        )
    }
    
    

    更新list 后,我使用setExtraData(new Date()) 告诉FlatList 重新渲染。因为新的时间和以前的不同。

    【讨论】:

    • 这也是监听更新平面列表变化的好方法。谢谢@tomfriwel
    • 嘿@tomfriwel 我遇到了同样的问题,我已经添加了extraData,但我在屏幕上看不到任何东西!我确定我的data 包含来自服务器的数据,因为当我在 Render() 中记录数据时,我看到了它们!
    • @DevAS 是extraData={this.state},而不是extraData={this.state.data}
    • @tomfriwel 这是extraData={this.state}
    • 这会重新渲染列表中的所有项目。我认为问题是如何更改列表中的单个项目。谁能提供一个完整的工作示例和代码来演示单个项目的更改?
    【解决方案2】:

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

    假设我的帖子包含这些属性:

    {
        postId: "-L84e-aHwBedm1FHhcqv",
        date: 1525566855,
        message: "My Post",
        uid: "52YgRFw4jWhYL5ulK11slBv7e583",
        liked: false,
        likeCount: 0,
        commentCount: 0
    }
    

    其中liked代表查看此帖的用户是否点赞了此帖,这将决定“赞”按钮的颜色(默认为灰色,liked == true为红色)


    以下是重新创建我的解决方案的步骤:将“发布”为Component 并将其呈现在FlatList 中。如果您没有传递给您的 Post 的任何道具,例如可能看似不浅相等的数组或对象,您可以使用 React 的 PureComponent。如果您不知道这意味着什么,只需使用常规的 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>                                                                       
        )                                                                               
      }                                                                                 
    }                                                                                   
    

    然后,创建一个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>
        )
      }
    }
    

    总结:

    1) 编写一个自定义组件(Post),用于渲染“FlatList”中的每一项

    2) 重写自定义组件(Post)函数的“shouldComponentUpdate”,告诉组件何时更新

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

    【讨论】:

    • 这个提示对我不起作用,请你帮忙我怎样才能找到一个好的解决方案,因为当我更新 flatlist 的单个项目时,所有项目都会重新渲染,如果我使用你的解决方案到时候什么都不会更新
    • @IqbalJan 你找到了只渲染更新项目的解决方案吗?
    • 你有这个的源代码吗?
    【解决方案3】:

    如果您在安卓上进行测试,请尝试关闭开发者模式。或者您是否在点击一些 API 并更新服务器上的帖子并更新 UI 中与服务器响应相对应的类似按钮?如果是这种情况,请告诉我,我也遇到过这个问题,我解决了。我还注释了您代码中不需要的倒数第二行。

    // 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 });
    }
    

    【讨论】:

    • 感谢您的回复。我不听服务器更改。我在后台更新数据库中的liked 属性,但我知道这需要一些时间,因此,我必须立即向用户提供反馈,这就是为什么我更新该帖子的liked 属性并使用@ 987654325@先在本地更改帖子。实际上在this.setState({ posts }) 行之后,我继续更新数据库中的记录,我忘了在我的问题中写下来,对此感到抱歉。我不知道为什么更新帖子需要大约 1 秒
    • 如果您在数据库操作之前执行 setState,那么它应该快速完成。能多贴一点涉及的代码吗?
    • 你是对的,我在做setState之前尝试修改数据库,我认为这是一个异步操作,所以我先做哪个操作没有关系。现在颜色几乎立即改变
    • 您还应该像这样处理这种情况: 1) 您点击了“赞”按钮并更新了 UI。 2) 如果在更新数据库中的条目时遇到错误。 3) 然后你也应该在 UI 中撤消更新。
    • @ShubhnikSingh 嗨,我有一个非常相似的问题,如果你可以请看一下。 stackoverflow.com/questions/65000877/…
    猜你喜欢
    • 1970-01-01
    • 2019-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多