You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native Hooks + Redux 如何避免页面重复渲染数据问题

问题:React Native + Redux Hooks 页面返回再进入重复加载数据导致列表重复

我是Redux新手,在React Native中用Hooks结合Redux开发时遇到问题:打开ArticleScreen页面后返回,再次进入该页面时,数据会被重新加载并渲染,导致列表出现重复数据。以下是我的相关代码,求帮忙排查问题。


ArticleScreen.js

const ArticleScreen = () => {
  const dispatch = useDispatch();
  const data = useSelector((state) => state.articleReducer.data);
  const isLoading = useSelector((state) => state.articleReducer.isLoading);
  const [loadingMore, setLoadingMore] = useState(false);
  const [page, setPage] = useState(1);
  const currentPage = useSelector((state) => state.articleReducer.currentPage);
  const totalPage = useSelector((state) => state.articleReducer.totalPage);
  const nextPage = useSelector((state) => state.articleReducer.nextPage);

  useEffect(() => {
    dispatch(fetchingArticle({ page: 1 }));
  }, [])

  const _renderItem = ({ item, index }) => {
    return (
      <TouchableOpacity
        key={`${item.id} ${page}`}
        style={{ marginBottom: 16 }}
      >
        <View style={{ flexDirection: 'row' }}>
          <View style={{ flex: 1 }}>
            <Text>{item.title}</Text>
          </View>
        </View>
      </TouchableOpacity>
    );
  };

  const handleLoadMoreData = () => {
    if (!isLoading) {
      setLoadingMore(true)
      if (nextPage <= totalPage) {
        dispatch(fetchingArticle(nextPage));
      } else {
        setLoadingMore(false)
      }
    }
  }

  return (
    <>
      <View>
        <FlatList
          data={data}
          renderItem={_renderItem}
          keyExtractor={item => `${item.id}`}
          onEndReached={handleLoadMoreData}
          onEndReachedThreshold={0.01}
          ListFooterComponent={
            loadingMore && (
              <View
                style={{
                  marginVertical: 30,
                }}>
                <ActivityIndicator
                  size="large"
                  color={Colors.onSurface}
                />
              </View>
            )
          }
        />
      </View>
    </>
  );
};

export default ArticleScreen;

store.js

const middleware = applyMiddleware(thunk);

// Root Reducer
const rootReducer = combineReducers({
  articleReducer: ArticleReducer,
});

// Redux Store
const store = createStore(
  rootReducer,
  middleware
)

export default store;

ArticleAction.js

export const fetchArticleRequest = () => ({
  type: 'FETCH_ARTICLE_REQUEST',
});

export const fetchArticleSuccess = (data) => ({
  type: 'FETCH_ARTICLE_SUCCESS',
  payload: data.data
});

export const fetchArticleFailure = (error) => ({
  type: 'FETCH_ARTICLE_FAILURE',
  payload: error
});

export const fetchingArticle = (page) => {
  return async dispatch => {
    dispatch(fetchArticleRequest());
    return apiRequest.get(URL.articles + '?page=' + page).then((response) => {
      dispatch(fetchArticleSuccess(response.data));
    })
      .catch((error) => {
        dispatch(fetchArticleFailure("Request Data Error"))
      })
  }
}

ArticleReducer.js

const initialState = {
  data: [],
  error: '',
  isLoading: false,
  refreshing: false,
  currentPage: 1,
  totalPage: 1,
  nextPage: 0,
  totalData: 0
};

const ArticleReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'FETCH_ARTICLE_REQUEST':
      return {
        ...state,
        isLoading: true,
        refreshing: true,
      };
    case 'FETCH_ARTICLE_SUCCESS':
      return {
        ...state,
        data: state.data.concat(action.payload.data),
        isLoading: false,
        refreshing: false,
        currentPage: action.payload.current_page,
        totalPage: action.payload.last_page,
        nextPage: action.payload.current_page + 1,

      };
    case 'FETCH_ARTICLE_FAILURE':
      return {
        ...state,
        error: action.error,
        isLoading: false,
        refreshing: false,
      };

    default:
      return state;
  }
};

export { ArticleReducer };

问题原因分析

  1. Reducer数据追加逻辑问题:每次进入页面触发第一页请求时,Reducer用state.data.concat()把新数据追加到原有数据后面,没有清空旧数据。Redux的状态是全局持久的,页面返回再进入时,之前的数据还存在Store中,导致新请求的第一页数据重复追加。
  2. 参数传递不一致:调用fetchingArticle时,有的地方传对象{page:1},有的地方传数字nextPage,会导致接口请求的page参数出错。
  3. 页面卸载未清理状态:页面返回时没有重置articleReducer的状态,再次进入时旧数据残留。

解决方案

方案1:请求第一页时替换数据而非追加

修改Action和Reducer,让Reducer能判断当前请求的是第一页,直接替换数据:

1. 更新ArticleAction.js,传递请求页码

export const fetchingArticle = (page) => {
  return async dispatch => {
    dispatch(fetchArticleRequest());
    return apiRequest.get(URL.articles + '?page=' + page).then((response) => {
      // 把当前请求的页码传入success action
      dispatch(fetchArticleSuccess({...response.data, currentRequestPage: page}));
    })
      .catch((error) => {
        dispatch(fetchArticleFailure("Request Data Error"))
      })
  }
}

2. 修改ArticleReducer.js的FETCH_ARTICLE_SUCCESS分支

case 'FETCH_ARTICLE_SUCCESS':
  return {
    ...state,
    // 第一页直接替换数据,非第一页追加数据
    data: action.payload.currentRequestPage === 1 
      ? action.payload.data 
      : state.data.concat(action.payload.data),
    isLoading: false,
    refreshing: false,
    currentPage: action.payload.current_page,
    totalPage: action.payload.last_page,
    nextPage: action.payload.current_page + 1,
  };

3. 统一ArticleScreen中的参数传递

useEffect(() => {
  dispatch(fetchingArticle(1)); // 直接传数字,和加载更多的参数类型统一
}, [])

方案2:页面卸载时重置状态

在页面离开时清空articleReducer的状态,避免残留旧数据:

1. 在ArticleAction.js新增重置Action

export const resetArticleState = () => ({
  type: 'RESET_ARTICLE_STATE',
});

2. 在ArticleReducer.js添加重置分支

case 'RESET_ARTICLE_STATE':
  return initialState; // 恢复初始状态

3. 在ArticleScreen.js中添加卸载副作用

useEffect(() => {
  dispatch(fetchingArticle(1));
  
  // 页面卸载时触发重置
  return () => {
    dispatch(resetArticleState());
  }
}, [])

额外优化

_renderItem中的key可以简化为item.id,因为FlatList的keyExtractor已经指定了唯一标识,无需拼接page,避免不必要的组件重渲染。


内容的提问来源于stack exchange,提问作者Quinn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 06:20:21