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 };
问题原因分析
- Reducer数据追加逻辑问题:每次进入页面触发第一页请求时,Reducer用
state.data.concat()把新数据追加到原有数据后面,没有清空旧数据。Redux的状态是全局持久的,页面返回再进入时,之前的数据还存在Store中,导致新请求的第一页数据重复追加。 - 参数传递不一致:调用
fetchingArticle时,有的地方传对象{page:1},有的地方传数字nextPage,会导致接口请求的page参数出错。 - 页面卸载未清理状态:页面返回时没有重置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
相关产品推荐
相关产品推荐

