如何使用远程数据实现VirtualizedList?

时间:2017-06-28 09:32:33

标签: reactjs react-native

我有一个生成通话记录列表的远程API。这当然可以很长,所以我需要延迟它。从RN文档我收集到,最好的选择是VirtualizedList。但是文档非常缺乏。例如,它谈论项目键。我可以提供我自己的函数(使用日期/时间值),但是getItem prop仍然要求从0开始的数组索引。那么RN使用密钥是什么?

另一件事是,我打印出对getItem和renderItem的调用,我看到一个非常奇怪的模式(我已经将initialNumToRender和maxToRenderPerBatch都设置为13)。这是应用程序启动时的全部内容,无需用户交互。我的getItemCount也返回15:

VirtualizedList.render() call
getItem 13 times: 0-12
getItem 0
renderItem 13 times: 0-12
VirtualizedList.render() call
getItem 13 times: 0-12
getItem 12
getItem 0
renderItem 12 times: 0-12
getItem 0
getItem 12
VirtualizedList.render() call
getItem 13 times: 0-12
getItem 12
getItem 0
renderItem 12 times: 0-12
getItem 0
getItem 12
getItem 0
getItem 12
(10 more like the 2 repeating above)
getItem 0-12
getItem 1
getItem 2
getItem 3
getItem 4
getItem 5
getItem 6
getItem 9
getItem 10
getItem 12   (Skipping some items here???)
onViewableItemsChanged, info= Object {viewableItems: Array(9), changed: Array(9)}
getItem 0-14
getItem 0-14
renderItem 0-14
onEndReached, info= Object {distanceFromEnd: 93.5}  (what is that value 93.5????)
getItem 0-12
getItem 0-11
onViewableItemsChanged, info= Object {viewableItems: Array(12), changed: Array(5)}
getItem 0-14
onEndReached, info= Object {distanceFromEnd: 221???}
getItem 0-11
getItem 0-10
onViewableItemsChanged, info= Object {viewableItems: Array(11), changed: Array(1)}
getItem 0-14

请注意,我还没有碰过屏幕。现在,当我向上滚动一点时,我得到以下事件:

getItem 0-12 
(repeats for around 20 times)
onViewableItemsChanged, info= Object {viewableItems: Array(12), changed: Array(1)}
getItem 0-12 
(repeats for around 20 times)

似乎我滚动的每个像素都会检索所有项目。

供参考,这是我的代码:

import Expo from 'expo';
import React, { PureComponent } from 'react';
import { Platform, FlatList, VirtualizedList, View, StyleSheet, Text } from 'react-native';
import { combineReducers } from 'redux';
import { ListItem } from 'react-native-elements';
import { connect } from 'react-redux';
import I18n from '../i18n';
import { takeEvery, all, call, put, select } from 'redux-saga/effects';
import RecentRow from '../components/RecentRow';
import { getUserId } from './Settings';
import { AppText, AppHeaderText } from '../components/AppText';

// action types
const RECENT_LOAD = 'RECENT_LOAD';
const RECENT_LOAD_OK = 'RECENT_LOAD_OK';
const RECENT_LOAD_ERROR = 'RECENT_LOAD_ERROR';

// action functions
export function recentLoad(offset) {
  return {
    type: RECENT_LOAD,
    offset: offset,
  };
}

// reducers
function recent(state = { offset: 1, data: [] }, action) {
  //console.log('recent', action);
  switch (action.type) {
    case RECENT_LOAD:
      return {
        ...state,
        offset: action.offset
      };

    case RECENT_LOAD_OK:
      return {
        ...state,
        data: action.data,
      };
    default:
      return state;
  }
}

// combined reducer
export const recentList = combineReducers({
  recent: recent,
});

export const getRecent = state => state.recent;
export const getAccount = state => state.settings.account;

function* recentLoadData(action) {
  const account = yield select(getAccount);
  const URL = `https://www.xxxxx.xx/api/calls.php?userrname=${account.email}&offset=${action.offset}`;
  try {
    const response = yield call(fetch, URL);
    if (response.status === 200) {
      result = yield call([response, 'json']);
      yield put({ type: RECENT_LOAD_OK, data: result });
    } else {
      yield put({ type: RECENT_LOAD_ERROR, error: response.status });
    }
  }
  catch(error) {
    console.log('error:', error);
    yield put({ type: RECENT_LOAD_ERROR, error: error })
  }
}

function* recentLoadSaga() {
  yield takeEvery('RECENT_LOAD', recentLoadData);
}

export function* recentSaga() {
  yield all([
    recentLoadSaga(),
  ])
}

class RecentList extends PureComponent {
  componentDidMount() {
    this.props.loadRecentCalls();
  }

  _renderItem = (item, userid) => {
    console.log('_renderItem', item);
    //return <RecentRow row={item} userid={userid} />
    return <ListItem title={item.item.name + ' ' + item.item.id } />
  }

  renderSeparator = () => {
    return (
      <View
        style={{
          height: 1,
          width: "95%",
          backgroundColor: "#CED0CE",
          marginLeft: "5%"
        }}
      />
    );
  };

  render() {
    console.log('RecentList.render()');
    return (
      <View style={styles.container}>
        <View style={styles.lineitem}>
          <View style={styles.header}>
            <AppHeaderText>{I18n.t('calls')}</AppHeaderText>
          </View>
        </View>
        <VirtualizedList
          data={this.props.recent.data}
          extraData={this.props}
          keyExtractor={item => item.somekey}
          renderItem={(item) => this._renderItem(item, this.props.userid)}
          initialNumToRender="13"
          maxToRenderPerBatch="13"
          //ItemSeparatorComponent={this.renderSeparator}
          ListEmptyComponent={ () => {
            return (
              <View style={styles.centerScreen}>
                <View>
                  <AppText>{I18n.t('nocallsfound')}</AppText>
                </View>
              </View>
            )
          }}
          ListFooterComponent={ () => {
            return (
              <Text>Footer goes here</Text>
            )
          }}
          ListHeaderComponent={ () => {
            return (
              <Text>Header goes here</Text>
            )
          }}
          getItem={ (data, index) => {
            console.log('getItem', index);
            return {name: 'My Name', id: index, somekey: index+1000};
          }}
          getItemCount={ (data, index) => {
            //console.log('getItemCount');
            return 15;
          }}
          onEndReached={ (info) => {
          console.log('onEndReached, info=', info);
          }}
          onViewableItemsChanged={ (info) => {
            console.log('onViewableItemsChanged, info=', info);
          }}
          />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'flex-start',

    backgroundColor: 'whitesmoke',
  },
  header: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',

    borderColor: 'grey',
    borderBottomWidth: 1,
    height: 40,
  },
  lineitem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',

    backgroundColor: 'white',
    padding: 5,
  },
  centerScreen: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    height: 300,
  }
});

const mapStateToProps = (state, props) => {
  return {
    recent: state.recentList.recent,
    userid: getUserId(state),
  };
};

const mapDispatchToProps = (dispatch, props) => {
  return {
    loadRecentCalls: () => dispatch(recentLoad(0)),
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(RecentList);

所以我的主要问题是,如何将所有这些放在一起延迟加载我的数据?

1 个答案:

答案 0 :(得分:1)

我使用redux-saga解决了这个问题,它比redux-thunk好得多。 这是我的代码,经过轻微编辑:

行动和减少者:

users

实际加载数据:

const LOAD = 'LOAD';
const LOAD_OK = 'LOAD_OK';
const LOAD_ERROR = 'LOAD_ERROR';
const REFRESH_START = 'REFRESH_START';

export function mylistRefreshStart() {
  return {
    type: REFRESH_START,
    append: false,
  };
}

export function mylistLoad() {
  return {
    type: LOAD,
    append: true,
  };
}

// reducer
export const mylist = (state = { offset: 0, limit: 50, data: [], refreshing: true }, action) => {
  //console.log('mylist:', action);
  switch (action.type) {
    case REFRESH_START:
      return {
        ...state,
        refreshing: true,
        offset: 0,
        limit: 50,
      };

    case LOAD_OK:
      return {
        ...state,
        data: action.append ? state.data.concat(action.data) : action.data,
        refreshing: false,
        limit: action.data.length !== 50 ? 0 : 50,
      };

    case LOAD_ERROR:
      return {
        ...state,
        refreshing: false,
      };

    default:
      return state;
  }
};

// selector
export const getMyData = state => state.mylist;

处理所有处理的传奇:

function* mylistLoadData(action) {
  const mylist = yield select(getMyData);
  if (mylist.limit === 0) {
    //console.log('nothing left to fetch');
    return;
  }
  try {
    const response = yield call(fetch, 'https://www.example.com/api/mylist.php', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        offset: action.append ? mylist.offset + mylist.data.length : mylist.offset,
        limit: mylist.limit,
      }),
    });
    if (response.status === 200) {
      result = yield call([response, 'json']);
      yield put({ type: LOAD_OK, data: result, append: action.append });
    } else {
      yield put({ type: LOAD_ERROR, error: response.status });
    }
  }
  catch(error) {
    console.log('error:', error);
    yield put({ type: LOAD_ERROR, error: error })
  }
}

渲染:

export function* mylistSaga() {
  yield takeLatest(REFRESH_START, mylistLoadData);
  yield takeLatest(LOAD, mylistLoadData);
}

并连接动作:

class MyList extends PureComponent {
  componentDidMount = () => {
    this.props.refreshStart();
  };

  onRefresh = () => {
    this.props.refreshStart();
  };

  onEndReached = () => {
    this.props.mylistLoad();
  };

  render = () => {
    return (
      <View style={styles.container}>
        <FlatList
          data={this.props.mylist.data}
          extraData={this.props}
          keyExtractor={item => item.id}
          refreshing={this.props.recent.refreshing}
          renderItem={(item) => this._renderItem(item)}
          ListEmptyComponent={ () => {
            if (this.props.mylist.refreshing) return null;
            return (
              <View style={styles.centerScreen}>
                <View>
                  <Text>Nothing found</Text>
                </View>
              </View>
            )
            }
          }
          onRefresh={() => this.onRefresh()}
          onEndReached={() => this.onEndReached()}
          />
      </View>
    );
  }
}

基本上我只是在我的商店中填充data []部分,让FlatList进行渲染所需的任何内容。