You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用React Router与useLocation的错误处理及路由传参疑问

井字棋游戏路由状态缺失问题解决

问题场景

开发井字棋游戏时,介绍页(路径/)提供了选择pickIndex的组件,点击<ButtonMultiplayer />或<ButtonCPU />按钮后,会携带该值跳转到/game页面。正常流程下代码可正常运行,但出现两种异常情况:

  1. 未选择pickIndex就手动修改URL直接访问/game,会抛出Cannot read properties of null (reading 'pickIndex')错误
  2. 在/game页面时,先跳转到无效URL触发404,再切回/game,pickIndex会重置为null

相关代码

介绍页组件(IntroView):

export default function IntroView() {
    const id = useId();
    const [isDisabled, setIsDisabled] = useState(true);
    const [pickIndex, setPickIndex] = useState("");
    const levelRef = useRef("")
    const navigate = useNavigate();

    return (
        <div className={styles.mainContainer}>
            <form>
                <div className={styles.pickMarkBox}>
                    <p>PICK PLAYER 1'S MARK</p>
                    <div className={styles.buttonBox}>
                        <ButtonMark
                            classInput="P1"
                            value="xButton"
                            classLabel={styles.player1}
                            markId={id + 'x'}
                            isActive={pickIndex === 'x'}
                            onClick={() => {
                                setPickIndex('x');
                                setIsDisabled(false);
                            }}
                        />
                        <ButtonMark
                            classInput="P2"
                            value="zeroButton"
                            classLabel={styles.player2}
                            markId={id + 'zero'}
                            isActive={pickIndex === 'zero'}
                            onClick={() => {
                                setPickIndex('zero');
                                setIsDisabled(false);
                            }}
                        />
                    </div>
                    <p>REMEMBER: X GOES FIRST</p>
                </div>
                < ButtonMultiplayer
                    value="NEW GAME VS PLAYER"
                    className={`${styles.gameMode} ${styles.newGameVsPlayer}`}
                    disabled={isDisabled}
                    onClick={(e) => {
                        e.preventDefault()
                        navigate("/game", { state: { gameMode: 'vsPlayer', pickIndex } });
                    }}
                />
                <ButtonCPU
                    disabled={isDisabled}
                    onChange={(e) => {
                        e.preventDefault()
                        levelRef.current = e.target.value
                        navigate("/game", { state: { gameMode: 'vsCPU', pickIndex, levelRef } })
                    }}
                    value={levelRef}
                />
            </form >
        </div >
    );
}

Game页面组件(Board):

export default function Board(src) {
   const mark = useLocation().state.pickIndex;
   // ... 其他代码
}

用户问题

  1. 如何处理用户直接访问/game的错误情况?
  2. 是否有必要放弃useLocation转而使用useParams?

无效尝试

  • 直接在组件内写条件判断跳转(违反React Hooks调用规则):
if (useLocation().state.pickIndex === null) {
  navigate("/")
}
  • 使用sessionStorage存储状态,但未生效

解决方案

1. 修复/game页面的状态校验与跳转

要遵循React Hooks的调用规则,通过useEffect处理路由状态校验,同时使用可选链避免空值报错:

import { useLocation, useNavigate, useEffect } from 'react-router-dom';

export default function Board() {
  const location = useLocation();
  const navigate = useNavigate();

  useEffect(() => {
    // 校验state是否存在,且pickIndex为有效值
    const validPickValues = ['x', 'zero'];
    if (!location.state?.pickIndex || !validPickValues.includes(location.state.pickIndex)) {
      navigate('/');
    }
  }, [location, navigate]);

  // 安全获取pickIndex,添加默认值兜底
  const mark = location.state?.pickIndex || 'x';
  // ... 其他游戏逻辑
}

说明:

  • useEffect会在路由变化时触发,确保每次进入/game都检查状态合法性
  • 可选链?.避免location.state为null时直接访问pickIndex报错
  • 增加有效值校验,防止传入非法的pickIndex

2. useLocation vs useParams的选择

两种方案适用于不同场景:

继续使用useLocation

  • 优势:状态存在内存中,不会暴露在URL里,适合不需要分享游戏链接的场景
  • 优化方案:结合sessionStorage持久化状态,避免刷新或从404切回时丢失:
    在介绍页选择pickIndex时存入缓存:
    // IntroView中ButtonMark的点击事件
    onClick={() => {
      setPickIndex('x');
      setIsDisabled(false);
      sessionStorage.setItem('tictactoe_pickIndex', 'x');
    }}
    
    在Board组件中读取缓存兜底:
    useEffect(() => {
      const savedPick = sessionStorage.getItem('tictactoe_pickIndex');
      if (!location.state?.pickIndex && !savedPick) {
        navigate('/');
      }
    }, [location, navigate]);
    
    const mark = location.state?.pickIndex || sessionStorage.getItem('tictactoe_pickIndex') || 'x';
    

改用useParams

  • 优势:参数存在URL中,用户可以直接分享带参数的游戏链接,直接访问也能获取到状态
  • 实现步骤:
    1. 修改路由配置,添加参数占位符:
      // 路由配置示例
      <Route path="/game/:pickIndex/:gameMode" element={<Board />} />
      
    2. 介绍页跳转时拼接URL参数:
      // ButtonMultiplayer的点击事件
      navigate(`/game/${pickIndex}/vsPlayer`);
      
    3. Board组件通过useParams获取参数:
      import { useParams } from 'react-router-dom';
      
      export default function Board() {
        const { pickIndex, gameMode } = useParams();
        // ... 其他逻辑
      }
      

无效尝试的原因

  1. 直接在条件语句中调用useLocation违反React Hooks规则:Hooks必须在组件顶层调用,不能嵌套在条件、循环等语句内
  2. sessionStorage未生效:大概率是未在选择pickIndex的时机存入缓存,或在Board组件中读取缓存的逻辑顺序错误

内容的提问来源于stack exchange,提问作者tomvas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 17:22:46