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

使用Framer Motion路由动画时React Context状态丢失问题求助

解决React Router v6 + Framer Motion路由动画导致Context状态丢失的问题

核心问题分析

你遇到的问题本质是路由切换时ChatLayout(Context Provider)被意外卸载重建:当给Routes组件设置key={location.pathname}时,从/chat/join跳转到/chat/room会触发Routes的完全重渲染,连带包裹在其中的ChatLayout被卸载,导致Context存储的聊天室信息丢失。

可行解决方案

调整组件层级和动画触发逻辑,既保留Context状态,又实现完整的进出动画:

1. 重构路由层级,让ChatLayout作为父路由组件

将ChatLayout设置为/chat路径的父路由,子路由对应join和room页面。这样子路由切换时,ChatLayout不会被卸载,Context状态自然保留。

2. 用location.key替代pathname触发动画

将AnimatePresence包裹在Routes外层,使用location.key作为Routes的key——这个key是React Router为每个路由跳转生成的唯一标识,既能触发AnimatePresence的动画逻辑,又不会因为子路由path变化导致父组件卸载。

完整代码示例

App组件(路由入口)
import { BrowserRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';
import ChatLayout from './ChatLayout';
import ChatJoin from './ChatJoin';
import ChatRoom from './ChatRoom';

function AnimatedRoutes() {
  const location = useLocation();
  
  return (
    <AnimatePresence exitBeforeEnter>
      {/* 使用location.key触发动画,而非pathname */}
      <Routes key={location.key}>
        {/* 父路由挂载ChatLayout,子路由通过Outlet渲染 */}
        <Route path="/chat" element={<ChatLayout />}>
          <Route path="join" element={<ChatJoin />} />
          <Route path="room" element={<ChatRoom />} />
        </Route>
      </Routes>
    </AnimatePresence>
  );
}

function App() {
  return (
    <Router>
      <AnimatedRoutes />
    </Router>
  );
}

export default App;
ChatLayout组件(带Context)
import { createContext, useContext, useState } from 'react';
import { Outlet } from 'react-router-dom';

// 创建Chat Context
const ChatContext = createContext();
export const useChatContext = () => useContext(ChatContext);

export default function ChatLayout() {
  // 聊天室状态,子组件共享
  const [chatRoomInfo, setChatRoomInfo] = useState(null);

  return (
    <ChatContext.Provider value={{ chatRoomInfo, setChatRoomInfo }}>
      {/* 这里可以放聊天室公共UI,比如顶部导航、房间信息栏 */}
      <div className="chat-layout">
        <Outlet /> {/* 渲染子路由页面 */}
      </div>
    </ChatContext.Provider>
  );
}
封装动画页面组件

创建一个通用的动画包裹组件,统一处理页面进出动画:

import { motion } from 'framer-motion';

export default function AnimatedPage({ children }) {
  return (
    <motion.div
      initial={{ opacity: 0, x: 20 }}
      animate={{ opacity: 1, x: 0 }}
      exit={{ opacity: 0, x: -20 }}
      transition={{ duration: 0.3 }}
      style={{ minHeight: '100vh' }}
    >
      {children}
    </motion.div>
  );
}
ChatJoin和ChatRoom组件(使用动画和Context)
// ChatJoin.jsx
import { useChatContext } from './ChatLayout';
import AnimatedPage from './AnimatedPage';
import { useNavigate } from 'react-router-dom';

export default function ChatJoin() {
  const { setChatRoomInfo } = useChatContext();
  const navigate = useNavigate();

  const handleJoin = () => {
    // 模拟设置聊天室信息
    setChatRoomInfo({ id: 'dev-room-01', name: '前端技术交流群' });
    navigate('/chat/room');
  };

  return (
    <AnimatedPage>
      <div className="chat-join">
        <h2>加入聊天室</h2>
        <button onClick={handleJoin}>进入测试房间</button>
      </div>
    </AnimatedPage>
  );
}
// ChatRoom.jsx
import { useChatContext } from './ChatLayout';
import AnimatedPage from './AnimatedPage';
import { useNavigate } from 'react-router-dom';

export default function ChatRoom() {
  const { chatRoomInfo } = useChatContext();
  const navigate = useNavigate();

  // 未选择房间时跳转回join页面
  if (!chatRoomInfo) {
    navigate('/chat/join');
    return null;
  }

  return (
    <AnimatedPage>
      <div className="chat-room">
        <h2>聊天室:{chatRoomInfo.name}</h2>
        <p>房间ID:{chatRoomInfo.id}</p>
        <button onClick={() => navigate('/chat/join')}>退出房间</button>
      </div>
    </AnimatedPage>
  );
}

方案为什么有效

  • Context状态保留:ChatLayout作为父路由组件,子路由切换时不会被卸载,因此Context中的状态不会丢失。
  • 完整动画支持:AnimatePresence包裹Routes,通过location.key触发动画,确保页面切换时退出动画先执行,再进入新页面的动画。
  • 兼容原有架构:不需要移除ChatContext或ChatLayout,完全符合你的需求。

为什么之前的方案无效

  1. 移除Routes的key:AnimatePresence无法检测到路由变化,因为Routes没有重新渲染,导致退出动画无法触发。
  2. AnimatePresence移至ChatLayout内:此时AnimatePresence仅包裹Outlet,子路由切换时,组件卸载的信号无法被AnimatePresence正确捕获,导致退出动画失效。

内容的提问来源于stack exchange,提问作者Sharif Velásquez Alzate

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 17:33:18