React Router v6中如何向Link组件传递函数
React Router 的 Link 组件的 state 参数底层依赖浏览器 History API,传入的值会经过结构化克隆算法处理,函数、Symbol、含循环引用的对象、类实例这类不可序列化的值无法被克隆,所以传递后在目标页通过 useLocation 取值会得到 null;可序列化的普通类型(字符串、数字、普通对象、数组等)可以正常传递,是因为符合结构化克隆的要求。
你尝试的 React Context 思路本身是可行的,之前代码不生效的核心原因是:RefreshDisplayContext.Provider 被写在了 Launchpad 组件内部,而 /login 路由对应的 Login 组件和 Launchpad 是路由平级关系,不在 Context Provider 的子树范围内,自然拿不到 Context 值。
调整方式如下:
方案1:修正Context作用域(推荐)
把 Context Provider 提升到路由层外层,让所有需要访问该方法的路由组件都处于 Provider 的子树中:
- 把 Context 定义、关联的共享状态统一提升到根入口组件,或抽到单独的公共Context文件:
// 入口文件 main.jsx import ReactDOM from "react-dom/client"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import React, { useState, useCallback } from "react"; import { Launchpad } from "./routes/Launchpad"; import { Login } from "./routes/Login"; // 导出Context供子组件引入使用 export const RefreshDisplayContext = React.createContext(() => {}); function App() { const [launchpadState, updatelaunchpadState] = useState({ userIsLoggedIn: false }); // 用useCallback包裹避免每次渲染函数引用变化 const refreshDisplay = useCallback((newState) => { updatelaunchpadState(prev => ({ ...prev, ...newState })); }, []); return ( <BrowserRouter> {/* Provider包裹整个路由区域,所有路由组件都能访问到Context值 */} <RefreshDisplayContext.Provider value={{ refreshDisplay, launchpadState }}> <Routes> <Route path="/" element={<Launchpad />} /> <Route path="/login" element={<Login />} /> </Routes> </RefreshDisplayContext.Provider> </BrowserRouter> ) } const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<App />);
- Launchpad 组件修改为直接消费Context里的状态和方法,移除内部多余的Provider和重复状态:
// routes/Launchpad.jsx import React, { useContext } from "react"; import { Link } from "react-router-dom"; import { RefreshDisplayContext } from "../main"; function Launchpad() { const { launchpadState } = useContext(RefreshDisplayContext); return ( <div> {launchpadState.userIsLoggedIn ? ( <p>User is Logged In </p> ) : ( <p>User is Not Logged In </p> )} <Link to="login"> Login via Child </Link> </div> ); } export { Launchpad };
- 原有Login组件不需要修改,就可以正常调用Context里的refreshDisplay方法更新状态。
方案2:传可序列化标识,目标页映射对应方法
如果不想用Context,可以遵循路由state的序列化设计要求,不要直接传函数,只传可序列化的字符串/数字标识,在目标路由页面提前引入所有可能用到的函数,根据传过来的标识匹配对应方法执行即可。
// 传参页 <Link to="/LoginScreen" state={{ funcType: 'refreshLogin' }} style={style.a} > Login </Link> // 目标页 LoginScreen import { useLocation } from "react-router-dom"; // 提前引入需要调用的方法 import { refreshLogin, refreshUserInfo } from "../utils/actions"; const funcMap = { refreshLogin, refreshUserInfo } function LoginScreen() { const location = useLocation(); const { funcType } = location.state || {}; const targetFunc = funcMap[funcType]; // 直接调用targetFunc即可 return <button onClick={targetFunc}>登录</button> }
方案3:使用全局状态管理
如果项目中已经接入了Redux、Zustand、Jotai这类全局状态库,可以直接把需要跨路由调用的方法存在全局Store中,跳转后直接从Store中取方法调用即可,逻辑和Context方案一致,适合更复杂的跨组件/跨路由状态共享场景。
注意:不要尝试通过JSON序列化/反序列化的方式传递函数,这类方式会丢失函数的闭包作用域,执行时会出现意料之外的报错,完全不符合React和路由的设计规范。
内容的提问来源于stack exchange,提问作者Utonyum

