当前位置: 首页 > 知识库问答 >
问题:

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

闾丘才哲
2024-05-06

如何优雅地使用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.jsfunction 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.jsimport 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);....

附:

  • github

共有1个答案

单于阳
2024-05-06

你的问题中涉及到了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的问题。

 类似资料:
  • 如何优雅地用CSS实现一个罗盘指示器? 实现效果大体类似于图片中这样: 一个圆盘里面包含四个箭头,四个箭头的尾部位于圆盘的中心,头部分别指向东、西、 南、北四个方面。 当前物体所朝向方面的箭头颜色与其他箭头颜色不同。 扩展: 在箭头头部用文字("N","S","E","W")标识方向。 用js获取设备的方向,在罗盘上实时显示当前设备的方向。 .... 我的实现思路: 首先实现一个箭头,一个箭头是由

  • 如何用next+react+websocket实现一个双人版的wordle游戏? 现在我用next+react复刻了wordle小游戏,现在我想要添加一个双人模式。 点击双人模式按钮,弹出一个modal,显示一个链接。将该链接分享给别人,当别人访问该链接的时候就,两个人就可以开始轮番猜词共同挑战一个wordle题目了。 需要实时更新同步两个人的操作,HTTP协议应该是做不到的,应该需要借助webs

  • 在讲述有关list的时候,提到做游戏的事情,后来这个事情一直没有接续。不是忘记了,是在想在哪个阶段做最合适。经过一段时间学习,看官已经不是纯粹小白了,已经属于python初级者了。现在就是开始做那个游戏的时候了。 游戏内容:猜数字游戏 太简单了吧。是的,游戏难度不大,不过这个游戏中蕴含的东西可是值得玩味的。 游戏过程描述 程序运行起来,随机在某个范围内选择一个整数。 提示用户输入数字,也就是猜程序

  • 如何优雅地覆盖antdesign的样式? 我想要把将这个button的border去掉(只是这个button,不影响其他button的样式)。 className or style都不行,Button组件没有提供这两个prop。倒是有一个classNames不知道是干什么的。 在index.css中添加以下规则,倒是可以去除Button上的border但是会影响所有的Button样式。 通过Con

  • 问题内容: 有没有一种方法可以优雅地停止和及其相关联的。 我正在努力实现的目标。 停止使用消息。 优雅地停下来。 等待长期运行的消费者,并在完成后确认。 我可以停止使用,但是长期运行的使用者不会成功完成,并且一旦ListenerContainer恢复后,已处理的消息也不会被确认,因此将再次进行处理。 输出量 邮件已处理,但未确认。 我也许可以使用来实现正常关机,但是是否可以验证被取消的使用者是否已

  • 人生太短,不能写没人会读的废话,如果你写了废话,没人会去读。所以好一点的文档是最好的。经理不会去理解这些东西,因为不好的文档会给他们错误的安全感以至于他们不敢依赖他们的程序员。如果一些人绝对坚持你真的在写没用的文档,就告诉他们“是的”,然后安静的找一份更好的工作。 没有其他事情比精确估计 把好的文档转为放松文档要求的估计 更为有效率。真相是冷酷而艰难的:文档,就像测试,会花比开发代码多几倍的时间。