React+Redux Toolkit+Redux-saga仅渲染最后一次moviedb API返回值问题
问题根因
1. 状态未隔离导致数据被覆盖
你当前的movieSlice只存储了单一的影片数据集,每次接口返回结果都会直接覆盖全局movie状态的results字段,所有Row组件共享同一个数据源,最终自然只会显示最后一次接口返回的内容。
2. 全量订阅导致重复渲染
所有Row组件都通过useSelector监听了整个state.movie,只要movie状态发生一次更新,所有订阅该状态的Row都会触发重渲染。如果有N个Row组件,就会发起N次接口请求,每次请求返回更新状态都会让N个Row各打印一次日志,最终就会出现N*N次重复打印的情况。
修复方案
第一步:改造movieSlice,按分类隔离存储数据
把state从单一对象改为键值对结构,用每个影片分类的唯一标识作为key,对应存储该分类的影片数据,避免不同分类的数据互相覆盖:
// movieSlice.js import { createSlice } from "@reduxjs/toolkit"; const movieSlice = createSlice({ name: "movie", initialState: {}, // 初始为对象,按分类key存储数据 reducers: { getMovie() {}, setMovie(state, action) { const { categoryKey, data } = action.payload; // 只更新对应分类的数据 state[categoryKey] = data; }, }, }); export const { getMovie, setMovie } = movieSlice.actions; export default movieSlice.reducer;
第二步:修改saga逻辑,透传分类标识
saga拿到接口返回结果后,把分类标识和对应数据一起提交到reducer:
// sagas/handlers/movie.js import { call, put } from "redux-saga/effects"; import { requestGetMovie } from "../requests/movies"; import { setMovie } from "../../ducks/movieSlice"; export function* handleGetMovie(action) { try { const { fetchLink, categoryKey } = action.payload; const response = yield call(requestGetMovie, { fetchLink }); const { data } = response; // 携带分类标识提交数据 yield put(setMovie({ categoryKey, data })); } catch (error) { console.log(error); } }
第三步:改造Row组件,仅订阅自身对应数据
dispatch请求时携带当前分类的唯一标识,取数时也只拉取当前分类对应的数据,避免被其他分类的状态更新触发多余重渲染:
// Row.js import React, { useEffect } from "react"; import styled from "styled-components"; import { useDispatch, useSelector, shallowEqual } from "react-redux"; import { getMovie } from "../redux/ducks/movieSlice"; function Row({ title, fetchUrl }) { const dispatch = useDispatch(); // 用fetchUrl作为分类的唯一标识,也可以用不重复的title代替 const categoryKey = fetchUrl; useEffect(() => { dispatch( getMovie({ fetchLink: fetchUrl, categoryKey: categoryKey }) ); }, [dispatch, fetchUrl, categoryKey]); // 仅订阅当前分类对应的影片数据,加shallowEqual减少不必要的重渲染 const movie = useSelector((state) => { return state.movie[categoryKey]?.results; }, shallowEqual); return ( <RowContainer> {title} <CardsContainer>{movie && movie[0]?.name}</CardsContainer> </RowContainer> ); } export default Row; const RowContainer = styled.div` color: white; `; const CardsContainer = styled.div` color: white; `;
修改完成后不同分类的影片数据会独立存储互不干扰,每个Row只会在自身对应数据更新时重渲染,即可解决数据覆盖和重复打印的问题。
内容的提问来源于stack exchange,提问作者Sarthak-bleepboop
相关产品推荐
相关产品推荐

