【问题标题】:Implementing infinite scroll with React/Redux and react-waypoint issue使用 React/Redux 和 react-waypoint 问题实现无限滚动
【发布时间】:2018-03-24 07:01:31
【问题描述】:

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

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

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

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

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

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

没有 Redux 的 react-waypoint 的简单示例是这样工作的:

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

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

所以问题是——我遇到的问题是 Redux 状态管理的问题,还是我没有正确实施我的 redux reducers/actions 还是...???

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

感谢您的帮助!谢谢!

减速器

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;

动作创作者

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

埃里克纳的回答

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

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

【问题讨论】:

  • 您能否发布您的代码(尤其是reducer 和组件),以便我们看看那里出了什么问题?
  • 刚刚添加。谢谢!

标签: reactjs redux infinite-scroll


【解决方案1】:

我想我看到了问题。

在您的组件中检查

{!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 将再次显示新照片。

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

【讨论】:

  • 非常感谢!这解决了问题!哦,我放心了。
  • @dejavu2012 很高兴我能帮助队友
【解决方案2】:

EDIT2

尝试将整个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);
    }
  });
}

而不是

photos.map(photo =>
  (<Photo ..

试试直接映射data.photos

data.photos.map(photo =>
  (<Photo ..

编辑

...action.data.photo] :
     action.data.photo;

你能确定它是action.data.photo,而不是action.data.photos,甚至只是action.data吗?您可以尝试将数据记录到控制台吗?

还有,

state.photos ? .. : ..

在这里,state.photos 将始终计算为 true-y 值,即使它是一个空数组。你可以改成

state.photos.length ? .. : ..

如果没有实际看到您如何在 reducersactions 中更新 photos,很难判断,但我怀疑这是 Redux 如何管理状态的问题。

当您从 ajax 请求中获取新照片时,传入的新照片应附加到 store 中的照片数组的末尾。

例如,如果当前 Redux 中的 photos: [&lt;Photo Z&gt;, &lt;Photo F&gt;, ...] store,而 action 中的新照片是 photos: [&lt;Photo D&gt;, &lt;Photo Q&gt;, ...],则 store 中的 photos 应该这样更新:

export default function myReducer(state = initialState, action) {
  switch (action.type) {
    case types.RECEIVE_PHOTOS:
      return {
        ...state,
        photos: [
          ...state.photos,
          ...action.photos,
        ],
      };
...

【讨论】:

  • 谢谢。我发布了我的减速器代码。我想我像你说的那样做,但它仍然有效 =( 我添加了 ...状态,它仍然有效。如果你看一下,我将不胜感激。谢谢!
  • 是的。我在上面回答了你,因为我需要添加图片。它在我帖子的底部。谢谢!
  • @dejavu2012 试试我在答案顶部添加的内容
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-09
  • 2021-04-12
  • 2021-11-12
  • 1970-01-01
相关资源
最近更新 更多