Как обновить один элемент в FlatList в React Native?

Внимание: я разместил ответ там, лично я считаю, что это лучшее решение на данный момент. Несмотря на то, что это не самый высокий рейтинг ответа, но в зависимости от результата, который я получаю, он очень эффективен.

---------------------------------------------Оригинальный вопрос ----------------------------------------------- --------

Предположим, я пишу клон Twitter, но намного проще. Я помещаю каждый элемент в FlatList и отображаю их.

Чтобы "понравиться" сообщению, я нажимаю кнопку "Мне нравится" на сообщении, и кнопка "Мне нравится" становится красной, я нажимаю ее снова, она становится серой.

Это то, что у меня пока есть: я сохраняю все загруженные посты в this.state, у каждого поста есть свойство "Мне нравится", которое является логическим и указывает, понравился ли этот пост пользователю или нет, когда пользователь нажимает "Мне нравится". Я захожу в 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 изменяется, и все сообщения перерисовываются, но какой другой подход я могу попробовать?

Ответ 1

Не поймите меня неправильно, ответ @ShubhnikSingh помог, но я отказался от него, потому что давно нашел лучшее решение этого вопроса и, наконец, не забыл опубликовать его здесь.

Предположим, что моя запись содержит следующие свойства:

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

Где liked показывает, понравился ли этот пост пользователю, просматривающему это сообщение, что определяет цвет кнопки "Мне нравится" (по умолчанию она серая, но красная, если liked == true)


Вот шаги для воссоздания моего решения: сделайте "Post" a 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) и передайте данные каждому дочернему элементу

Ответ 2

Вы можете установить extraData в FlatList:

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

При изменении элемента state.posts или state.posts FlatList будет повторно отображаться.

Из FlatList # extradata:

Свойство маркера для повторного рендеринга списка (поскольку оно реализует PureComponent). Если какая-либо из ваших функций renderItem, Header, Footer и т.д. Зависит от чего-либо, находящегося за пределами данных, прикрепите его здесь и обработайте с неизменным вниманием.

Ответ 3

Если вы тестируете на андроиде, попробуйте отключить режим разработчика. Или вы нажимаете какой-то API и обновляете сообщение на сервере и обновляете подобную кнопку в пользовательском интерфейсе, соответствующую ответу сервера? Если это так, скажите мне, я тоже столкнулся с этим, и я решил это. Также я прокомментировал вторую последнюю строку в вашем коде, которая не нужна.

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