2015-11-23 2 views
7

Я пытаюсь реализовать бесконечный свиток в React Native. Ниже приведен исходный код компонента:React Native ListView - rowHasChanged не срабатывает

var React = require('react-native'); 
var server = require('../server'); 
var Post = require('./Post'); 
var SwipeRefreshLayoutAndroid = require('./SwipeRefreshLayout'); 
var backEvent = null; 
var lastPostId = ""; 
var isLoadingMore = false; 
var isLoadingTop = false; 
var onEndReachedActive = false; 

var { 
    StyleSheet, 
    ListView, 
    View, 
    Text, 
    Image, 
    ProgressBarAndroid, 
    BackAndroid 
} = React; 

class Stream extends React.Component { 

constructor(props) { 
    super(props); 
    this.ds = new ListView.DataSource({ 
     rowHasChanged: (row1, row2) => { 
      console.log("rowHasChenged FIRED!!"); 
      return false; 
     } 
    }); 

    this.state = { 
     dataSource: this.ds.cloneWithRows(['loader']), 
     hasStream: false, 
     posts: [] 
    }; 

} 

componentDidMount() { 

    BackAndroid.addEventListener('hardwareBackPress',() => { 
     this.props.navigator.jumpBack(); 
     return true; 
    }.bind(this)); 

    server.getStream('', '', 15).then((res) => { 

     lastPostId = res[res.length-1].m._id; 

     this.setState({ 
      posts: res, 
      hasStream: true, 
      dataSource: this.ds.cloneWithRows(res) 
     },() => onEndReachedActive = true); 
    }) 
} 

onRefresh() { 
    var posts = this.state.posts; 
    var firstPost = posts[0].m._id; 

    console.log(this.state.dataSource._rowHasChanged); 

    isLoadingTop = true; 

    server.getStream('', firstPost, 4000) 
     .then(res => { 
      console.log(posts.length); 
      posts = res.concat(posts); 
      console.log(posts.length); 
      this.setState({ 
       dataSource: this.ds.cloneWithRows(posts), 
       posts 
      },() => { 
       this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh(); 
       isLoadingTop = false; 
      }); 
     }).catch((err) => { 
      isLoadingTop = false; 
     }) 

} 

onEndReached(event) { 

    if(!onEndReachedActive) return; 

    if(this.state.loadingMore || this.state.isLoadingTop)return; 
    isLoadingMore = true; 
    var posts = this.state.posts; 
    server.getStream(posts[posts.length-1].m._id, '', 15) 
     .then(res => { 
      console.log('received posts'); 
      posts = posts.concat(res); 
      lastPostId = posts[posts.length-1].m._id; 
      this.setState({ 
       dataSource: this.ds.cloneWithRows(posts), 
       posts 
      },()=>isLoadingMore = false); 
     }) 
} 

renderHeader() { 
    return (
     <View style={styles.header}> 
      <Text style={styles.headerText}>Header</Text> 
     </View> 
    ) 
} 

renderRow(post) { 

    if(post === 'loader') { 
     return (
      <ProgressBarAndroid 
       styleAttr="Large" 
       style={styles.spinnerBottom}/> 
     ) 
    } 

    let hasLoader = post.m._id === lastPostId; 

    let loader = hasLoader ? 
     <ProgressBarAndroid 
      styleAttr="Large" 
      style={styles.spinnerBottom}/> : null; 

    return (
     <View> 
      <Post 
       post={post}/> 
      {loader} 
     </View> 
    ) 
} 

render() { 


    return (
       <ListView 
        style={styles.mainContainer} 
        dataSource={this.state.dataSource} 
        renderRow={this.renderRow.bind(this)} 
        onEndReached={this.onEndReached.bind(this)} 
        onEndReachedThreshold={1} 
        pageSize={15} /> 
    ); 
} 
} 

Проблема заключается в том, что всякий раз, когда я добавить (или перед именем) новые данные, то rowHasChanged метод DataSource не срабатывает. Он просто повторно отображает каждую строку, даже если ничего не изменилось (кроме новых данных). Любая идея, почему метод обходит?

ответ

12

Edit: Передайте функцию SetState, чтобы избежать условий гонки

Я просто понял это. Если у вас такая же проблема, проверьте, в какой момент вы меняете свое состояние с помощью нового dataSource. Шахта была такой:

this.setState({ 
    dataSource: this.ds.cloneWithRows(posts) 
}); 

Вместо этого вы должны всегда использовать dataSource из предыдущего состояния, как это:

this.setState(state => ({ 
    dataSource: this.state.dataSource.cloneWithRows(posts) 
})) 

Ура!

+0

Кто-нибудь знает, если это документ в любом месте в документах React Native? Я не могу найти его конкретно упомянутым, и это кажется важным ... –

+0

Если вы используете текущее состояние для обновления состояния, вы должны использовать форму 'this.setState ((state) => ({dataSource: state.dataSource.cloneWithRows (сообщения)})); '. Передача функции setState снижает риск состояния гонки. – jtolds

+0

@jtolds Отредактировано, спасибо! –

3

Это сработало для меня, надеюсь, это поможет. Я создал новый DataSource и назначен обновленные данные ему на изменении состояния следующим образом: `

var dataSource = new ListView.DataSource(
    {rowHasChanged: (r1, r2) => (r1 !== r2)}); 

    this.setState({ dataSource : dataSource.cloneWithRows(posts) }); 

Теперь новые данные назначены, и представление отображается правильно. Обратите внимание, что массив записей, который теперь назначен, содержит обновленные данные. Все еще интересно, хотя, если это лучший способ сделать это, но он работает!

+0

Это не ответ. Код в исходном вопросе. Новые данные также назначаются как нормальные, а представление также отображается правильно. Вопрос в том, почему rowHasChanged не вызывается, когда появляются новые данные. Чтобы воспроизвести его, вам нужно поместить console.log в строкуHasChanged и, пожалуйста, введите console.log в render(). И вы обнаружите, что с помощью этого кода вся строка, независимо от старого, нового, изменилась не изменена, будет повторно отображена после вызова setState. На правильный ответ отвечает сам Радо. –

1

Я согласен, что кажется, чтобы понять, что вы всегда должны использовать DataSource из предыдущего состояния.

Но когда я SetState этого пути, rowHasChanged вызывается для всех строк, однако, rowHasChanged всегда возвращает ложных и строк не визуализируются ??? Зачем?

// This is callback handler that the ListView DetailView will 
    // call when a ListView item is edited 
    onChange(waypoint: Object){ 
    console.log('Callback: rowNumber= ', waypoint.rowNumber); 
    console.log('  length(m)= ', waypoint.distance.meters); 

    var itemListChanged = this.state.itemList; 
    itemListChanged[waypoint.rowNumber-1] = waypoint; 
    this.setState({ 
      dataSource: this.state.dataSource.cloneWithRows(itemListChanged), 
    }); 
    }, 

Если я SetState этот путь, renderRow вызывается для всех строк, безусловно, не rowHasChanged никогда не вызывается. Что правильно?

this.setState({ 
    dataSource: ds.cloneWithRows(itemListChanged), 
}); 

ListView, DataSource, и реагировать на родной являются трудно кривой обучения, исходя из C#/C/C++.

+0

Может быть, вы можете опубликовать свой конструктор, функцию rowHasChanged. –

+0

Предполагается, что вы не сделали «пройденное значение», но «передано по ссылке», что означает, что this.state.dataSource уже изменен на одно и то же значение (поскольку они указывают на тот же адрес RAM). Скажем, вы назначаете яблочную корзину a, выставляете яблоко, выбираете яблоко и помещаете апельсин в корзину, вы думаете, что это сравнение яблока с апельсином, но на самом деле вы сравниваете вещи в корзине с корзиной a. Вы сравниваете оранжевый с оранжевым. –

+0

Решение: сначала вы назначаете яблоко в корзину b, а затем, когда вы сравниваете вещи в корзине, положите оранжевый в корзину c. для сравнения вам нужно иметь более 1 корзины. Короче говоря, вы помещаете реквизит в состояние, как только реквизит изменится, состояние уже изменено, прежде чем вы вызываете setState. –

0

для тех, кто по-прежнему возникают проблемы с rowHasChanged под названием, но по-прежнему возвращается ложных следующих фрагментов может помочь

источника данных инициализируются, как обычно:

let ds = new ListView.DataSource ({ 

    rowHasChanged: (a, b) => { 
    const changed = (a !== b) 
    return changed 
    } 

}) 

this.data = [] 

this.state = { 
    listDataSource: ds.cloneWithRows(this.data) 
} 

здесь функция, которая будет обновить строку

updateRow = (row, rowId, sectionId) => { 

    // make a shallow clone from the stored data array 
    let blob = this.data.concat() 

    // modify the row, since we are using the triple equal operator, we need to make sure we are giving it a new object (new ref) 
    blob[rowId] = Object.assign({}, blob[rowId], {label: blob[rowId].label + '..cape..deh'}) 

    // tell react to update the source 
    this.setState({ 
    listDataSource: this.state.listDataSource.cloneWithRows(blob) 
    },() => { 
    // we need to update our data storage here! after the state is changed 
    this.data = blob 
    }) 

} 
Смежные вопросы