reactjs - 使用React/Redux实现无限滚动和react-waypoint问题

标签 reactjs redux infinite-scroll

我正在努力使用我的测试 React/Redux 应用程序实现无限滚动。

简单来说它是如何工作的:

1) 在 componentDidMount 上,我调度一个操作,该操作在从 API 获取 100 张照片后设置 Redux 状态。所以我得到了 Redux 状态下的照片数组。

2)我实现了react-waypoint,因此当您滚动到这些照片的底部时,它会触发一个方法,该方法会调度另一个操作来获取更多照片并将它们“附加”到照片数组中,然后...

据我了解 - 状态发生了变化,因此 redux 正在触发 setState 并且组件完全重绘,所以我需要再次开始滚动,但现在有 200 张照片。当我再次到达路径点时,一切都会再次发生,组件完全重新渲染,我现在需要从顶部滚动 300 张照片。

这当然不是我想要的工作方式。

在没有 Redux 的情况下,react-waypoint 的简单示例的工作方式如下:

1) 您获取第一张照片并设置组件初始状态 2) 滚动到路径点后,它会触发一个方法,该方法向 api 发出另一个请求,构造新的照片数组(附加新获取的照片)并(!)使用新的照片数组调用 setState。

而且它有效。没有完全重新渲染组件。滚动位置保持不变,新项目出现在航路点下方。

所以问题是 - 是我遇到 Redux 状态管理问题还是我没有正确实现我的 redux reducer /操作还是......???

为什么要在React Waypoint Infinite Scroll example中设置组件状态(没有 Redux)按照我想要的方式工作(不重绘整个组件)?

非常感谢任何帮助!谢谢!

reducer

import { combineReducers } from 'redux';

const data = (state = {}, action) => {
  if (action.type === 'PHOTOS_FETCH_DATA_SUCCESS') {
    const photos = state.photos ?
      [...state.photos, ...action.data.photo] :
      action.data.photo;

    return {
      photos,
      numPages: action.data.pages,
      loadedAt: (new Date()).toISOString(),
    };
  }
  return state;
};

const photosHasErrored = (state = false, action) => {
  switch (action.type) {
    case 'PHOTOS_HAS_ERRORED':
      return action.hasErrored;
    default:
      return state;
  }
};

const photosIsLoading = (state = false, action) => {
  switch (action.type) {
    case 'PHOTOS_IS_LOADING':
      return action.isLoading;
    default:
      return state;
  }
};

const queryOptionsIntitial = {
  taste: 0,
  page: 1,
  sortBy: 'interestingness-asc',
};
const queryOptions = (state = queryOptionsIntitial, action) => {
  switch (action.type) {
    case 'SET_TASTE':
      return Object.assign({}, state, {
        taste: action.taste,
      });
    case 'SET_SORTBY':
      return Object.assign({}, state, {
        sortBy: action.sortBy,
      });
    case 'SET_QUERY_OPTIONS':
      return Object.assign({}, state, {
        taste: action.taste,
        page: action.page,
        sortBy: action.sortBy,
      });
    default:
      return state;
  }
};

const reducers = combineReducers({
  data,
  photosHasErrored,
  photosIsLoading,
  queryOptions,
});

export default reducers;

Action 创建者

import tastes from '../tastes';

// Action creators
export const photosHasErrored = bool => ({
  type: 'PHOTOS_HAS_ERRORED',
  hasErrored: bool,
});

export const photosIsLoading = bool => ({
  type: 'PHOTOS_IS_LOADING',
  isLoading: bool,
});

export const photosFetchDataSuccess = data => ({
  type: 'PHOTOS_FETCH_DATA_SUCCESS',
  data,
});

export const setQueryOptions = (taste = 0, page, sortBy = 'interestingness-asc') => ({
  type: 'SET_QUERY_OPTIONS',
  taste,
  page,
  sortBy,
});

export const photosFetchData = (taste = 0, page = 1, sort = 'interestingness-asc', num = 500) => (dispatch) => {
  dispatch(photosIsLoading(true));
  dispatch(setQueryOptions(taste, page, sort));
  const apiKey = '091af22a3063bac9bfd2e61147692ecd';
  const url = `https://api.flickr.com/services/rest/?api_key=${apiKey}&method=flickr.photos.search&format=json&nojsoncallback=1&safe_search=1&content_type=1&per_page=${num}&page=${page}&sort=${sort}&text=${tastes[taste].keywords}`;
  // console.log(url);
  fetch(url)
    .then((response) => {
      if (!response.ok) {
        throw Error(response.statusText);
      }
      dispatch(photosIsLoading(false));
      return response;
    })
    .then(response => response.json())
    .then((data) => {
      // console.log('vvvvv', data.photos);
      dispatch(photosFetchDataSuccess(data.photos));
    })
    .catch(() => dispatch(photosHasErrored(true)));
};

我还包括了渲染照片的主要组件,因为我认为它可能与我将该组件“连接”到 Redux 存储这一事实有关......

import React from 'react';
import injectSheet from 'react-jss';
import { connect } from 'react-redux';
import Waypoint from 'react-waypoint';

import Photo from '../Photo';
import { photosFetchData } from '../../actions';
import styles from './styles';

class Page extends React.Component {

  loadMore = () => {
    const { options, fetchData } = this.props;
    fetchData(options.taste, options.page + 1, options.sortBy);
  }

  render() {
    const { classes, isLoading, isErrored, data } = this.props;

    const taste = 0;

    const uniqueUsers = [];
    const photos = [];
    if (data.photos && data.photos.length > 0) {
      data.photos.forEach((photo) => {
        if (uniqueUsers.indexOf(photo.owner) === -1) {
          uniqueUsers.push(photo.owner);
          photos.push(photo);
        }
      });
    }

    return (
      <div className={classes.wrap}>
        <main className={classes.page}>

          {!isLoading && !isErrored && photos.length > 0 &&
            photos.map(photo =>
              (<Photo
                key={photo.id}
                taste={taste}
                id={photo.id}
                farm={photo.farm}
                secret={photo.secret}
                server={photo.server}
                owner={photo.owner}
              />))
          }
        </main>
        {!isLoading && !isErrored && photos.length > 0 && <div className={classes.wp}><Waypoint onEnter={() => this.loadMore()} /></div>}
        {!isLoading && !isErrored && photos.length > 0 && <div className={classes.wp}>Loading...</div>}
      </div>
    );
  }
}

const mapStateToProps = state => ({
  data: state.data,
  options: state.queryOptions,
  hasErrored: state.photosHasErrored,
  isLoading: state.photosIsLoading,
});

const mapDispatchToProps = dispatch => ({
  fetchData: (taste, page, sort) => dispatch(photosFetchData(taste, page, sort)),
});

const withStore = connect(mapStateToProps, mapDispatchToProps)(Page);

export default injectSheet(styles)(withStore);

回答Eric Na

state.photos 是一个对象,我只是检查它是否存在于该状态中。抱歉,在我的例子中,我只是试图简化事情。

action.data.photo 肯定是一个数组。 Api 就是这样命名的,我没有考虑重命名它。

我提供了一些来自 React 开发工具的图片。

  1. Here is my initial state after getting photos
  2. Here is the changed state after getting new portion of photos
  3. There were 496 photos in the initial state, and 996 after getting additional photos for the first time after reaching waypoint
  4. here is action.data

所以我想说的是,照片已被获取并附加,但它仍然触发组件的整个重新渲染......

最佳答案

我想我看到了问题所在。

在您的组件中检查

{!isLoading && !isErrored && photos.length > 0 &&
            photos.map(photo =>
              (<Photo
                key={photo.id}
                taste={taste}
                id={photo.id}
                farm={photo.farm}
                secret={photo.secret}
                server={photo.server}
                owner={photo.owner}
              />))
          }

一旦您发出另一个 api 请求,请在操作创建器中将 isLoading 设置为 true。这告诉 react 删除整个照片组件,然后一旦将其设置为 false 再次 react 将显示新照片。

您需要在底部添加一个加载器,并且不要在获取后删除整个照片组件,然后再次渲染它。

关于reactjs - 使用React/Redux实现无限滚动和react-waypoint问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49462149/

相关文章:

javascript - Redux:从 Web 服务访问状态的正确方法?

jquery - 将图像延迟加载绑定(bind)到ajax请求后插入的新图像

php - 无限滚动一次加载所有项目?

reactjs - 使用 React/Relay/Typescript 跨片段重用组件的最佳实践是什么?

javascript - React-Redux 将 google Places api 变成了 promise

javascript - 处理 Redux 中的大量文本输入更改

javascript - jQuery 弹出窗口无法在无限滚动的页面上工作

reactjs - 开 Jest 错误,需要 Babel “^7.0.0-0” ,但加载了 “6.26.3”

reactjs - 使用 Electron-builder 构建 React-Electron 应用程序,index.js 加载到 pre 标签内

javascript - 使用 Jest 和 Enzyme 测试 react-router v4