最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Can I use a variable index in a useSelector call? - Stack Overflow

programmeradmin0浏览0评论

Suppose I have a long list of playerData in my redux store, and some players' data gets updated very often. Let's say I have a Player component and inside of it I want to access that player's specific data. To do so, I would need to access a specific index of the playerData array.

Selector functions are supposed to be pure functions, and my understanding is that pure functions cannot alter their return values based on outside data. Am I correct in believing that the following three examples are therefore invalid?

//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>
}

If those three are invalid, the only way I can think to access a player's data is by selecting the entire playerData array and then getting a specific index afterwards.

// Using example B as a jumping off point...
const ActivePlayer = ({}) => {
    const [activePlayerIndex, setActivePlayerIndex] = useState(0);
    const allPlayerDatas = useSelector(state => state.playerDataArray);
    const activePlayerData = allPlayerData[activePlayerIndex];
    return <div>{activePlayerData.name}</div>
}

I would like to avoid this if possible, as that would (if I'm understanding correctly) cause the component to rerender every single time any playerData is changed, not just when the playerData it's trying to display is changed.

Ideally, I would like the component only to rerender when the index or the playerDataArray[index] changes. Is there a way to accomplish that?

Suppose I have a long list of playerData in my redux store, and some players' data gets updated very often. Let's say I have a Player component and inside of it I want to access that player's specific data. To do so, I would need to access a specific index of the playerData array.

Selector functions are supposed to be pure functions, and my understanding is that pure functions cannot alter their return values based on outside data. Am I correct in believing that the following three examples are therefore invalid?

//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>
}

If those three are invalid, the only way I can think to access a player's data is by selecting the entire playerData array and then getting a specific index afterwards.

// Using example B as a jumping off point...
const ActivePlayer = ({}) => {
    const [activePlayerIndex, setActivePlayerIndex] = useState(0);
    const allPlayerDatas = useSelector(state => state.playerDataArray);
    const activePlayerData = allPlayerData[activePlayerIndex];
    return <div>{activePlayerData.name}</div>
}

I would like to avoid this if possible, as that would (if I'm understanding correctly) cause the component to rerender every single time any playerData is changed, not just when the playerData it's trying to display is changed.

Ideally, I would like the component only to rerender when the index or the playerDataArray[index] changes. Is there a way to accomplish that?

Share Improve this question edited Mar 3 at 18:36 Drew Reese 204k18 gold badges245 silver badges273 bronze badges asked Mar 3 at 17:29 EKWEKW 2,15415 silver badges24 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 3

Pure function means that calling the function with the same arguments, would always return the same result, and that the function doesn't have side effects. Calling a selector with the current state, and a prop doesn't make it impure - if the same state and prop are used again, the return value would be identical.

All your examples are valid. In the redux docs for useSelecor() examples you have the "Using props via closure to determine what to extract" example:

export const TodoListItem = (props) => {
  const todo = useSelector((state) => state.todos[props.id])
  return <div>{todo.text}</div>
}

In your scenario both values actually come from the state, so you can generate a single selector. Create 2 simple selectors, and combine them:

const getWinningPlayerIndex = state => state.winningPlayerIndex;

const getPlayerData = state => state.playerData;

const getWinnerData = state => getPlayerData(state)[getWinningPlayerIndex(state)];


const WinnerPlayer = () => {
  const winnerPlayerData = useSelector(getWinnerData);
  return <div>{winnerPlayerData.name}</div>
}

If you want to avoid exposing the internal shape of your application state to your components, it's useful to define your selectors separately. i.e.

function playerDataSelector(state, { index }) {
    return state.playerDataArray[index];
}

function winningPlayerDataSelector(state) {
    const { playerData, winningPlayerIndex } = state;

    return playerData[winningPlayerIndex];
}

Or you can use reselect to define your selectors. The createSelector function takes two arguments: an array of dependencies, and a result function. The dependencies are used as a caching mechanism; the result is only recalculated if the values of the dependencies change. It's not strictly necessary in this instance since your result functions have O(1) time, but it becomes very useful with more complex result functions.

import { createSelector } from 'reselect';

const playerDataSelector = createSelector(
    [
        (state) => state.playerDataArray,
        (state, props) => props.index,
    ],
    (playerDataArray, index) => playerDataArray[index],
);

const winningPlayerDataSelector = createSelector(
    [
        (state) => state.playerDataArray,
        (state) => state.winningPlayerIndex,
    ],
    (playerDataArray, winningPlayerIndex) => playerDataArray[winningPlayerIndex],
);

Finally plug the selectors into your code:

//A
const Player = ({ index }) => {
    const thisPlayerData = useSelector((state) => playerDataSelector(state, { index }));

    return <div>{thisPlayerData.name}</div>
}

//B
const ActivePlayer = ({}) => {
    const [activePlayerIndex, setActivePlayerIndex] = useState(0);
    const thisPlayerData = useSelector((state) => playerDataSelector(state, { index: activePlayerIndex }));

    return <div>{activePlayerData.name}</div>
}

//C
const WinnerPlayer = ({}) => {
    const winnerPlayerData = useSelector(winningPlayerDataSelector);

    return <div>{winnerPlayerData.name}</div>
}
发布评论

评论列表(0)

  1. 暂无评论