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

新手上路,请多包涵

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

——————————————— 原始问题--- ————————————————– –

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

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

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

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

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

原文由 K.Wu 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 698
2 个回答

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

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

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

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


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

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

原文由 K.Wu 发布,翻译遵循 CC BY-SA 4.0 许可协议

您可以在 extraData 中设置 --- FlatList

 <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 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题