繁体   English   中英

如何在 axios 调用后使用 useReducer 获取反应组件以重新渲染?

[英]How to get react component with useReducer to rerender after axios call?

我正在尝试使用 useReducer 挂钩学习状态管理,因此我构建了一个调用 pokeAPI 的简单应用程序。 该应用程序应显示随机口袋妖怪,并在按下“捕获另一个”按钮时向屏幕添加更多口袋妖怪。

但是,它会在从 axios 调用填充 Card 之前使用初始化的空 Card 对象重新渲染组件。 我已经尝试了至少 3 种基于 stackoverflow 帖子的不同解决方案。

在每次尝试中,我都得到了相同的结果:应用程序显示一个未定义的卡片,即使状态是更新的而不是未定义的,它只是在重新渲染后稍微更新。 再次单击时,先前的未定义会正确呈现,但现在有一张新卡显示为未定义。

我仍然掌握了 React 钩子(没有双关语!)、异步编程和一般的 JS 的窍门。

这是应用程序: https : //stackblitz.com/edit/react-ts-mswxjv?file=index.tsx

这是我第一次尝试的代码:

//index.tsx

const getRandomPokemon = (): Card => {
  var randomInt: string;
  randomInt = String(Math.floor(898 * Math.random()));
  let newCard: Card = {};
  PokemonDataService.getCard(randomInt)
    .then((response) => {
        //omitted for brevity
    })
    .catch((error) => {
      //omitted
    });

  PokemonDataService.getSpecies(randomInt)
    .then((response) => {
      //omitted
    })
    .catch((error) => {
      //omitted
    });
  return newCard;
};

const App = (props: AppProps) => {
  const [deck, dispatch] = useReducer(cardReducer, initialState);

function addCard() {
    let newCard: Card = getRandomPokemon();
    dispatch({
      type: ActionKind.Add,
      payload: newCard,
    });
  }
  return (
    <div>
      <Deck deck={deck} />
      <CatchButton onClick={addCard}>Catch Another</CatchButton>
    </div>
  );
};

//cardReducer.tsx
export function cardReducer(state: Card[], action: Action): Card[] {
  switch (action.type) {
    case ActionKind.Add: {
      let clonedState: Card[] = state.map((item) => {
        return { ...item };
      });
      clonedState = [...clonedState, action.payload];
      return clonedState;
    }
    default: {
      let clonedState: Card[] = state.map((item) => {
        return { ...item };
      });
      return clonedState;
    }
  }
}


//Deck.tsx
//PokeDeck and PokeCard are styled-components for a ul and li
export const Deck = ({ deck }: DeckProps) => {
  useEffect(() => {
    console.log(`useEffect called in Deck`);
  }, deck);
  
  return (
    <PokeDeck>
      {deck.map((card) => (
        <PokeCard>
          <img src={card.image} alt={`image of ${card.name}`} />
          <h2>{card.name}</h2>
        </PokeCard>
      ))}
    </PokeDeck>
  );
};

我还尝试使调用 Axios 的函数成为一个 promise,这样我就可以将调度调用与 .then 链接起来。

//index.tsx
function pokemonPromise(): Promise<Card> {
  var randomInt: string;
  randomInt = String(Math.floor(898 * Math.random()));
  let newCard: Card = {};
  PokemonDataService.getCard(randomInt)
    .then((response) => {
      // omitted
    })
    .catch((error) => {
      return new Promise((reject) => {
        reject(new Error('pokeAPI call died'));
      });
    });

  PokemonDataService.getSpecies(randomInt)
    .then((response) => {
        // omitted
    })
    .catch((error) => {
      return new Promise((reject) => {
        reject(new Error('pokeAPI call died'));
      });
    });
  return new Promise((resolve) => {
    resolve(newCard);
  });
}

const App = (props: AppProps) => {
  const [deck, dispatch] = useReducer(cardReducer, initialState);

  function asyncAdd() {
    let newCard: Card;
    pokemonPromise()
      .then((response) => {
        newCard = response;
        console.log(newCard);
      })
      .then(() => {
        dispatch({
          type: ActionKind.Add,
          payload: newCard,
        });
      })
      .catch((err) => {
        console.log(`asyncAdd failed with the error \n ${err}`);
      });
  }

  return (
    <div>
      <Deck deck={deck} />
      <CatchButton onClick={asyncAdd}>Catch Another</CatchButton>
    </div>
  );
};

我还尝试使用 useEffect 钩子让它有副作用地调用它

//App.tsx
const App = (props: AppProps) => {
  const [deck, dispatch] = useReducer(cardReducer, initialState);
  const [catchCount, setCatchCount] = useState(0);


  useEffect(() => {
    let newCard: Card;
    pokemonPromise()
      .then((response) => {
        newCard = response;
      })
      .then(() => {
        dispatch({
          type: ActionKind.Add,
          payload: newCard,
        });
      })
      .catch((err) => {
        console.log(`asyncAdd failed with the error \n ${err}`);
      });
  }, [catchCount]);
  
   return (
    <div>
      <Deck deck={deck} />
      <CatchButton onClick={()=>{setCatchCount(catchCount + 1)}>Catch Another</CatchButton>
    </div>
  );
};

所以你的代码有几件事,但最后一个版本最接近正确。 通常,您希望在 useEffect 中调用 promise。 如果您希望它被调用一次,请使用空的 [] 依赖项数组。 https://reactjs.org/docs/hooks-effect.html(ctrl+f “一次”并阅读注释,它不是那么可见)。 任何时候 dep 数组发生变化,代码都会运行。

注意:您必须更改对 Pokemon 服务的调用,因为您正在运行两个异步调用而不等待它们中的任何一个。 您需要使getRandomPokemon异步并等待两个调用,然后返回您想要的结果。 (此外,您正在返回 newCard 但未在通话中为其分配任何内容)。 首先通过在像我的示例代码这样的承诺中返回假数据来测试这一点,然后在遇到问题时集成 api。

在您的承诺中,它返回一个 Card,您可以直接在调度中使用它(从响应中,您不需要额外的步骤)。 您的 onclick 也用括号错误地书写。 这是我编写的一些示例代码,似乎可以正常工作(使用占位符函数):

type Card = { no: number };
function someDataFetch(): Promise<void> {
  return new Promise((resolve) => setTimeout(() => resolve(), 1000));
}
async function pokemonPromise(count: number): Promise<Card> {
  await someDataFetch();
  console.log("done first fetch");
  await someDataFetch();
  console.log("done second fetch");
  return new Promise((resolve) =>
    setTimeout(() => resolve({ no: count }), 1000)
  );
}

const initialState = { name: "pikachu" };
const cardReducer = (
  state: typeof initialState,
  action: { type: string; payload: Card }
) => {
  return { ...state, name: `pikachu:${action.payload.no}` };
};

//App.tsx
const App = () => {
  const [deck, dispatch] = useReducer(cardReducer, initialState);
  const [catchCount, setCatchCount] = useState(0);
  useEffect(() => {
    pokemonPromise(catchCount)
      .then((newCard) => {
        dispatch({
          type: "ActionKind.Add",
          payload: newCard
        });
      })
      .catch((err) => {
        console.log(`asyncAdd failed with the error \n ${err}`);
      });
  }, [catchCount]);

  return (
    <div>
      {deck.name}
      <button onClick={() => setCatchCount(catchCount + 1)}>
        Catch Another
      </button>
    </div>
  );
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM