Reactjs 使用React/Redux和React航路点问题实现无限滚动

Reactjs 使用React/Redux和React航路点问题实现无限滚动,reactjs,redux,infinite-scroll,Reactjs,Redux,Infinite Scroll,我正在努力用我的test React/Redux应用程序实现无限滚动 下面简单介绍一下它的工作原理: 1) 在componentDidMount上,我发送了一个动作,在从API获取100张照片后设置Redux状态。所以我得到了Redux州的照片阵列 2) 我实现了react航路点,因此当您滚动到这些照片的底部时,它会触发一个方法,该方法将发送另一个操作,以获取更多照片并将其“附加”到照片数组中,然后 据我所知,状态已更改,因此redux正在启动setState,组件将完全重新绘制,因此我需要再次

我正在努力用我的test React/Redux应用程序实现无限滚动

下面简单介绍一下它的工作原理:

1) 在componentDidMount上,我发送了一个动作,在从API获取100张照片后设置Redux状态。所以我得到了Redux州的照片阵列

2) 我实现了react航路点,因此当您滚动到这些照片的底部时,它会触发一个方法,该方法将发送另一个操作,以获取更多照片并将其“附加”到照片数组中,然后

据我所知,状态已更改,因此redux正在启动setState,组件将完全重新绘制,因此我需要再次开始滚动,但现在它有200张照片。当我再次到达航路点时,一切都再次发生,组件完全重新启动,我现在需要从顶部滚动300张照片

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

没有Redux的react航路点的简单示例如下:

export default function myReducer(state = initialState, action) {
  switch (action.type) {
    case types.RECEIVE_PHOTOS:
      return {
        ...state,
        photos: [
          ...state.photos,
          ...action.photos,
        ],
      };
...
1) 获取第一张照片并将组件设置为初始状态 2) 滚动到航路点后,它触发一个方法,该方法向api发出另一个请求,构造新照片数组(附加新获取的照片),并使用新照片数组(!)调用setState

它是有效的。组件没有完全重新渲染。滚动位置保持不变,新项目显示在航路点下方

所以问题是-我遇到的问题是Redux状态管理问题,还是我实施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)));
};
动作创作者

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);
从“React”导入React;
从“react jss”导入表单;
从'react redux'导入{connect};
从“反应航路点”导入航路点;
从“../Photo”导入照片;
从“../../actions”导入{photofetchdata};
从“./styles”导入样式;
类页扩展了React.Component{
加载更多=()=>{
const{options,fetchData}=this.props;
fetchData(options.taste,options.page+1,options.sortBy);
}
render(){
const{classes,isLoading,isErrored,data}=this.props;
常量味觉=0;
const uniqueUsers=[];
常量照片=[];
如果(data.photos&&data.photos.length>0){
data.photos.forEach((照片)=>{
if(uniqueUsers.indexOf(photo.owner)=-1){
uniqueUsers.push(photo.owner);
照片。推送(照片);
}
});
}
返回(
{!isLoading&&!iErrored&&photos.length>0&&
photos.map(照片=>
())
}
{!isLoading&&!iErrored&&photos.length>0&&this.loadMore()}/>}
{!isLoading&&!isErrored&&photos.length>0&&Loading…}
);
}
}
常量mapStateToProps=状态=>({
数据:state.data,
选项:state.queryOptions,
haserroed:state.photoshaserroed,
isLoading:state.photosIsLoading,
});
const mapDispatchToProps=调度=>({
fetchData:(味道、页面、排序)=>dispatch(PhotoFetchData(味道、页面、排序)),
});
const withStore=connect(mapStateToProps、mapDispatchToProps)(第页);
导出默认图纸(样式)(带存储);
回答Eric Na

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

action.data.photo肯定是一个数组。Api给它起了这样的名字,我没有想过要给它改名

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

  • 所以我想说的是,这些照片是被提取和附加的,但它仍然会触发组件的整个重新渲染…

    EDIT2

    尝试对整个
    uniqueUsers
    部分进行注释(稍后让我们担心用户的唯一性)

    而不是

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

    编辑

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

    而且

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

    state.photos.length ? .. : ..
    

    在没有看到你如何在
    还原器
    操作
    中更新
    照片
    的情况下很难判断,但我怀疑这是Redux如何管理状态的问题

    当您从ajax请求中获得新照片时,新照片应该附加到
    存储区中照片数组的末尾

    例如,如果当前Redux
    store
    中的
    photos:[,…]
    ,并且
    action
    中的新照片是
    photos:[,…]
    ,则
    store
    中的
    照片应如下更新:

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

    我想我看到了问题所在

    在您的组件中检查

    {!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}
                  />))
              }
    
    {!isLoading&&!iErrored&&photos.length>0&&
    photos.map(照片=>
    ())
    }
    
    一旦发出另一个api请求,在action creator中将
    isLoading
    设置为
    true
    。这告诉react删除整个照片组件,然后将其设置为
    false
    后,react将再次显示新照片


    您需要在底部添加一个加载程序,而不是在抓取后删除整个照片组件,然后再次进行渲染。

    您可以发布代码(尤其是减速机和组件),以便我们能够看到哪里出了问题吗?刚刚添加。谢谢谢谢我发布了我的减速机代码。我想我是按照你说的做的,但它仍然工作相同
    {!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}
                  />))
              }