如何优雅地使用react做一个wordle小游戏?

如何优雅地使用react做一个wordle小游戏?

wordle游戏wiki
在线wordle游戏

我用reat + tailwindcss实现了该游戏,但是比较纠结什么什么时候该使用React.useCallback,自定义的hooks是否合理?

我写了一个自定义的hook,用来实现数据持久化(刷新之后仍可以保持刷新之前的游戏状态)

  • 这里的name,initial永远都不会变化。
  • 初始状态先从localstorage中获取,然后调用initial函数(包装了hook返回的set函数。

    • 期望这个effect只会在初始加载的时候调用一次。
    • 为什么不直接在React.useState(() => { window.localstore...})获取初始状态呢?

      • 因为这是一个next项目,在服务端渲染的时候是没有window这个对象的。
      • 避免本地的初次渲染和服务端的初次渲染不匹配。
    • 当状态改变的时候,就更新localstorage中的值。
// src/hooks/useLocalStore.js
function useLocalStore(state, name, initial) {
  React.useEffect(() => {
    let store = window.localStorage.getItem(name);
    if (store) initial(JSON.parse(store));
  }, [name, initial]);

  React.useEffect(() => {
    window.localStorage.setItem(name, JSON.stringify(state));
  }, [name, state]);
}

自定义hook的时候,需要将函数先用React.useCallback"包"起来。否则的话,上面提到的预期就达不到了。每次渲染的时候都会产生一个新的intial,effect每次都会执行。

// src/hooks/useLocalStore.js
....
import useLocalStore from "./useLocalStore";
function useGameState(notify, statistic) {
  const [gameState, dispatch] = React.useReducer(reducer, initialState);
  const initial = React.useCallback(
    (state) => dispatch({ type: "load-state", state: state }),
    []
  );
  useLocalStore(gameState, "gameState", initial);
....

当自定义的hooks需要传入一个函数的时候,我们首先需要考虑是否需要将这个函数用React.useCallback"包"起来呢?其实,我们也可以不考虑,都用React.useCallback包起来应该就万事大吉了。最后在我的项目中到处可见React.useCallack,总觉得有些奇怪,是我自定义hooks有问题吗?

另外自定义hook返回的值又作为参数传给了另一个hook,这样写合理吗?

解释下面代码:

  • 当想要改变游戏的状态时,可能需要给出提示,如"游戏进行中,不能开始新一轮游戏"、"必须包含字母S"..。
  • 当一局游戏结束的时候,需要提示“胜利”或者“失败”,并将当前局的获胜情况记录到gameStatistic状态中。
  • 所以才试图将useNotificationuseGameStatistc这两个自定义hook的返回值传给另一个useGameState这个自定义hook

    // src/app/page.js
    import React from "react";
    import useGameState from "@/hooks/useGameState";
    import useNotification from "@/hooks/useNotification";
    import useGameStatistic from "@/hooks/useGameStatistic";
    
    export default function Home() {
    const [message, notify] = useNotification();
    const [gameStatistic, statistic] = useGameStatistic();
    const [gameState, startGame, changeGameStateBasedOnKey, toggleHardMode] =
      useGameState(notify, statistic);
    ....

附:

阅读 581
avatarAI BotBETA

你的问题中涉及到了React编程中许多重要的概念,包括自定义Hook的使用、React.useCallback的使用、以及状态管理和持久化。下面是我对你问题的回答:

  1. 何时使用React.useCallback?

React.useCallback的主要用途是返回一个记忆化的回调函数。当依赖项没有改变时,useCallback返回的函数在多次渲染之间将保持不变。这在某些情况下非常有用,例如当我们将函数作为props传递给子组件时,子组件的props没有改变,React可以通过props的引用稳定性来避免不必要的重新渲染。

在你的例子中,useGameState的initial函数需要被useCallback包裹,因为你不希望每次渲染都创建一个新的函数,这会导致useLocalStore的effect每次都执行,这显然不是你期望的行为。

  1. 自定义的Hook是否合理?

你的自定义Hook看起来非常合理。useLocalStore这个Hook封装了localStorage的逻辑,使得你的组件代码更加清晰,也更加容易测试。useGameState、useNotification和useGameStatistic这三个Hook分别封装了游戏状态、通知和游戏统计的逻辑,这有助于保持组件的逻辑清晰和可重用。

  1. 自定义Hook返回的值又作为参数传给了另一个Hook,这样写合理吗?

这样做是合理的。在React中,Hook之间并没有严格的依赖关系,你可以自由地在一个Hook中使用另一个Hook的返回值。在你的例子中,useGameState需要useNotification和useGameStatistic的返回值作为参数,这完全符合React的Hook设计原则。

你的代码看起来已经很好地实现了Wordle游戏,而且你还考虑到了状态持久化的问题,这非常棒。总的来说,你的代码结构清晰,逻辑合理,已经很好地遵循了React的最佳实践。

不过,有一点你可能需要注意,那就是useLocalStore这个Hook可能会在组件卸载后仍然尝试更新localStorage,这可能会导致错误。为了避免这个问题,你可以在useLocalStore的第二个effect中添加一个依赖项来检查组件是否仍然挂载:

React.useEffect(() => {
  window.localStorage.setItem(name, JSON.stringify(state));
}, [name, state, mounted]);

const mounted = React.useRef(true);

React.useEffect(() => {
  return () => {
    mounted.current = false;
  };
}, []);

这样,当组件卸载时,mounted.current将会变为false,第二个effect将不会执行,从而避免了在组件卸载后更新localStorage的问题。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题