React Redux中能否在useSelector调用中使用变量索引?
我的Redux Store里存储了一长列playerData,部分玩家的数据更新非常频繁。我有一个Player组件,需要访问特定玩家的数据,这需要从playerData数组中获取对应索引的项。
我知道Redux的选择器必须是纯函数,而纯函数不能依赖外部数据改变返回值。那我认为下面三个示例的写法都无效,这个判断对吗?
//A const Player = ({ index }) => { const thisPlayerData = useSelector(state => state.playerDataArray[index]); return <div>{thisPlayerData.name}</div> } //B const ActivePlayer = ({}) => { const [activePlayerIndex, setActivePlayerIndex] = useState(0); const activePlayerData = useSelector(state => state.playerDataArray[activePlayerIndex]); return <div>{activePlayerData.name}</div> } //C const WinnerPlayer = ({}) => { const winningPlayerIndex = useSelector(state => state.winningPlayerIndex); const winnerPlayerData = useSelector(state => state.playerData[winningPlayerIndex]); return <div>{winnerPlayerData.name}</div> }
如果这三个写法确实有问题,我能想到的唯一办法是先取出整个playerData数组,再获取对应索引的项:
// 以示例B为基础修改 const ActivePlayer = ({}) => { const [activePlayerIndex, setActivePlayerIndex] = useState(0); const allPlayerDatas = useSelector(state => state.playerDataArray); const activePlayerData = allPlayerDatas[activePlayerIndex]; return <div>{activePlayerData.name}</div> }
但我不想这么做,因为只要任何玩家的数据发生变化,组件都会重新渲染,而不是只在目标玩家的数据变化时才渲染。
我希望组件仅在index或者playerDataArray[index]变化时重新渲染,有没有办法实现?
首先明确:你认为三个示例都无效的想法不对,这三种写法都是合法且符合Redux规则的。
为什么这三个写法是合规的?
Redux对useSelector中选择器的纯函数要求是:对于相同的State输入,必须返回相同的输出,而非不能依赖组件的Props或内部State。
- 示例A的选择器依赖组件的
indexProps:只要index不变、对应位置的玩家数据不变,选择器就会返回相同值,满足纯函数要求; - 示例B的选择器依赖组件内部的
activePlayerIndexState:同样满足“相同输入(State + activePlayerIndex)对应相同输出”的纯函数规则; - 示例C的第二个选择器依赖第一个选择器拿到的
winningPlayerIndex:本质是基于当前State的推导,属于纯函数范畴。
另外,useSelector默认采用浅比较判断返回值是否变化。只要你的playerDataArray是遵循Redux不可变更新原则维护的,那么只有当目标玩家的对象引用或值发生变化时,组件才会重新渲染——这正是你想要的效果。
如何实现精准渲染的优化?
如果想进一步明确依赖、避免潜在的不必要渲染,可以使用createSelector(来自@reduxjs/toolkit或reselect库)创建带参数的记忆化选择器:
import { createSelector } from '@reduxjs/toolkit'; // 根选择器:取出整个玩家数组 const selectPlayerDataArray = state => state.playerDataArray; // 创建带索引参数的记忆化选择器 const selectPlayerByIndex = createSelector( [selectPlayerDataArray, (_, index) => index], (playerDataArray, index) => playerDataArray[index] ); // 组件中使用 const Player = ({ index }) => { const thisPlayerData = useSelector(state => selectPlayerByIndex(state, index)); return <div>{thisPlayerData.name}</div>; };
这种方式会缓存选择器的计算结果,只有当目标索引对应的玩家数据变化,或者index本身变化时,才会重新计算并触发组件渲染。
另外,示例C的写法可以合并为一个选择器,减少useSelector的调用次数:
const WinnerPlayer = () => { const winnerPlayerData = useSelector(state => { const winningIndex = state.winningPlayerIndex; return state.playerData[winningIndex]; }); return <div>{winnerPlayerData.name}</div>; };
这样更简洁,同时能保证只有winningPlayerIndex或对应玩家数据变化时,组件才会重新渲染。
内容的提问来源于stack exchange,提问作者EKW

