跳到主要内容

编码✅

如何在 React 中实现类似 Vue keep-alive 的功能?

答案

在 React 中可以通过多种方式实现类似 Vue keep-alive 的组件缓存功能:

方案一:使用 display 控制显示隐藏

import React, { useState } from 'react';
import KeepAliveContainer from './KeepAliveContainer';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';

export default function App() {
  const [currentComponent, setCurrentComponent] = useState('A');

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
      <h1>React Keep-Alive 示例</h1>
      
      <div style={{ marginBottom: '20px' }}>
        <button 
          onClick={() => setCurrentComponent('A')}
          style={{
            padding: '10px 20px',
            margin: '0 10px',
            backgroundColor: currentComponent === 'A' ? '#007acc' : '#f0f0f0',
            color: currentComponent === 'A' ? 'white' : 'black',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          组件 A
        </button>
        <button 
          onClick={() => setCurrentComponent('B')}
          style={{
            padding: '10px 20px',
            margin: '0 10px',
            backgroundColor: currentComponent === 'B' ? '#007acc' : '#f0f0f0',
            color: currentComponent === 'B' ? 'white' : 'black',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          组件 B
        </button>
      </div>

      <div style={{ 
        border: '1px solid #ddd', 
        borderRadius: '8px', 
        padding: '20px',
        backgroundColor: '#f9f9f9',
        minHeight: '300px'
      }}>
        <h3>当前活跃组件: {currentComponent}</h3>
        
        <KeepAliveContainer>
          <ComponentA isActive={currentComponent === 'A'} />
          <ComponentB isActive={currentComponent === 'B'} />
        </KeepAliveContainer>
      </div>

      <div style={{ 
        marginTop: '20px', 
        padding: '15px',
        backgroundColor: '#e3f2fd',
        borderRadius: '4px',
        fontSize: '14px'
      }}>
        <strong>📝 说明:</strong>
        <ul style={{ margin: '10px 0', paddingLeft: '20px' }}>
          <li>切换组件时,之前的组件状态会被保持</li>
          <li>组件不会重新挂载,只是控制显示/隐藏</li>
          <li>输入框的内容和计数器的值都会被保留</li>
          <li>这样可以避免重复的网络请求和状态初始化</li>
        </ul>
      </div>
    </div>
  );
}

方案二:使用 Portal 实现更复杂的缓存

import { useState, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';

function AdvancedKeepAlive({ children, isActive, cacheKey }) {
const cacheRef = useRef(new Map());
const containerRef = useRef();

useEffect(() => {
if (!containerRef.current) {
containerRef.current = document.createElement('div');
containerRef.current.style.display = 'none';
document.body.appendChild(containerRef.current);
}

return () => {
if (containerRef.current) {
document.body.removeChild(containerRef.current);
}
};
}, []);

useEffect(() => {
if (containerRef.current) {
containerRef.current.style.display = isActive ? 'block' : 'none';
}
}, [isActive]);

if (!containerRef.current) return null;

return createPortal(children, containerRef.current);
}

方案三:使用第三方库 react-activation

import KeepAlive, { AliveScope } from 'react-activation';

function App() {
return (
<AliveScope>
<Router>
<Switch>
<Route exact path="/">
<KeepAlive cacheKey="home">
<Home />
</KeepAlive>
</Route>
<Route path="/about">
<KeepAlive cacheKey="about">
<About />
</KeepAlive>
</Route>
</Switch>
</Router>
</AliveScope>
);
}

答案解析

该题考察如下知识点:

  1. React 组件生命周期:理解组件挂载、卸载和重新渲染的机制
  2. 状态管理:如何在组件切换时保持状态不丢失
  3. Portal 技术:使用 createPortal 将组件渲染到指定 DOM 节点
  4. 缓存策略:设计合理的组件缓存和清理机制

核心实现思路

  • 使用 display: none/block 控制组件显示隐藏而不是卸载
  • 利用 Map 或对象存储组件实例和状态
  • 通过唯一的 cacheKey 标识需要缓存的组件
  • 提供缓存清理机制防止内存泄漏

与 Vue keep-alive 的对比

特性Vue keep-aliveReact 实现
原生支持框架内置需要自己实现
API 简洁性简单易用相对复杂
缓存控制include/exclude自定义逻辑
性能高度优化取决于实现方式

如何实现转场动画?

答案

React 中可以通过多种方式实现转场动画,以下是常见的实现方案:

方案一:使用 CSS Transitions + 状态控制

import React, { useState } from 'react';
import CSSTransition from './CSSTransition';

export default function App() {
  const [isVisible, setIsVisible] = useState(true);
  const [currentView, setCurrentView] = useState('home');

  const views = {
    home: { title: '🏠 首页', color: '#4CAF50', content: '欢迎来到首页!这里有最新的内容和动态。' },
    about: { title: '📋 关于', color: '#2196F3', content: '这是关于页面,了解更多关于我们的信息。' },
    contact: { title: '📞 联系', color: '#FF9800', content: '联系我们获取更多信息和支持。' }
  };

  return (
    <div style={{ 
      padding: '20px', 
      fontFamily: 'Arial, sans-serif',
      maxWidth: '800px',
      margin: '0 auto'
    }}>
      <h1>React 转场动画示例</h1>
      
      {/* 基础显示/隐藏动画 */}
      <div style={{ 
        marginBottom: '40px',
        padding: '20px',
        border: '1px solid #ddd',
        borderRadius: '8px',
        backgroundColor: '#f9f9f9'
      }}>
        <h2>基础显示/隐藏动画</h2>
        <button
          onClick={() => setIsVisible(!isVisible)}
          style={{
            padding: '10px 20px',
            backgroundColor: '#007acc',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            marginBottom: '20px'
          }}
        >
          {isVisible ? '隐藏' : '显示'} 内容
        </button>

        <CSSTransition isVisible={isVisible}>
          <div style={{
            padding: '20px',
            backgroundColor: '#e3f2fd',
            borderRadius: '8px',
            border: '2px solid #2196F3'
          }}>
            <h3>🎉 动画内容</h3>
            <p>这个内容会有平滑的进入和退出动画效果。</p>
            <p>使用了 CSS transition 和 React 状态控制。</p>
          </div>
        </CSSTransition>
      </div>

      {/* 视图切换动画 */}
      <div style={{ 
        padding: '20px',
        border: '1px solid #ddd',
        borderRadius: '8px',
        backgroundColor: '#f9f9f9'
      }}>
        <h2>视图切换动画</h2>
        <div style={{ marginBottom: '20px' }}>
          {Object.keys(views).map((key) => (
            <button
              key={key}
              onClick={() => setCurrentView(key)}
              style={{
                padding: '10px 20px',
                margin: '0 5px',
                backgroundColor: currentView === key ? views[key].color : '#f0f0f0',
                color: currentView === key ? 'white' : 'black',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer'
              }}
            >
              {views[key].title}
            </button>
          ))}
        </div>

        <div style={{ 
          position: 'relative', 
          minHeight: '200px',
          overflow: 'hidden',
          border: '1px solid #ccc',
          borderRadius: '8px'
        }}>
          {Object.keys(views).map((key) => (
            <CSSTransition
              key={key}
              isVisible={currentView === key}
              animationType="slide"
            >
              <div style={{
                position: 'absolute',
                top: 0,
                left: 0,
                right: 0,
                padding: '20px',
                backgroundColor: views[key].color + '20',
                borderLeft: `4px solid ${views[key].color}`
              }}>
                <h3 style={{ color: views[key].color, margin: '0 0 10px 0' }}>
                  {views[key].title}
                </h3>
                <p style={{ margin: 0, color: '#333' }}>
                  {views[key].content}
                </p>
              </div>
            </CSSTransition>
          ))}
        </div>
      </div>

      <div style={{ 
        marginTop: '20px', 
        padding: '15px',
        backgroundColor: '#fff3e0',
        borderRadius: '4px',
        fontSize: '14px'
      }}>
        <strong>📝 动画说明:</strong>
        <ul style={{ margin: '10px 0', paddingLeft: '20px' }}>
          <li><strong>淡入淡出:</strong>使用 opacity 变化实现平滑的显示隐藏</li>
          <li><strong>滑动效果:</strong>使用 transform translateX 实现左右滑动</li>
          <li><strong>缩放效果:</strong>使用 transform scale 实现缩放动画</li>
          <li><strong>组合动画:</strong>同时使用多个 CSS 属性创建复合效果</li>
        </ul>
      </div>
    </div>
  );
}

方案二:使用 react-transition-group

import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './transition.css';

function TransitionDemo() {
const [items, setItems] = useState([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' }
]);

const addItem = () => {
const newItem = {
id: Date.now(),
text: `Item ${items.length + 1}`
};
setItems([...items, newItem]);
};

const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};

return (
<div>
<button onClick={addItem}>Add Item</button>
<TransitionGroup>
{items.map(item => (
<CSSTransition
key={item.id}
timeout={300}
classNames="item"
>
<div className="item">
{item.text}
<button onClick={() => removeItem(item.id)}>×</button>
</div>
</CSSTransition>
))}
</TransitionGroup>
</div>
);
}

对应的 CSS:

.item-enter {
opacity: 0;
transform: translateX(-100%);
}

.item-enter-active {
opacity: 1;
transform: translateX(0);
transition: all 300ms ease-in-out;
}

.item-exit {
opacity: 1;
transform: translateX(0);
}

.item-exit-active {
opacity: 0;
transform: translateX(100%);
transition: all 300ms ease-in-out;
}

方案三:使用 Framer Motion

import { motion, AnimatePresence } from 'framer-motion';

function FramerTransition() {
const [isVisible, setIsVisible] = useState(true);

return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle
</button>

<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.3 }}
className="animated-box"
>
<h3>Animated Content</h3>
<p>This content has smooth transitions!</p>
</motion.div>
)}
</AnimatePresence>
</div>
);
}

方案四:自定义 Hook 实现动画

import { useState, useEffect } from 'react';

function useTransition(isVisible, duration = 300) {
const [shouldRender, setShouldRender] = useState(isVisible);
const [isAnimating, setIsAnimating] = useState(false);

useEffect(() => {
if (isVisible) {
setShouldRender(true);
requestAnimationFrame(() => {
setIsAnimating(true);
});
} else {
setIsAnimating(false);
const timer = setTimeout(() => {
setShouldRender(false);
}, duration);
return () => clearTimeout(timer);
}
}, [isVisible, duration]);

return { shouldRender, isAnimating };
}

function CustomTransition({ isVisible, children }) {
const { shouldRender, isAnimating } = useTransition(isVisible);

if (!shouldRender) return null;

return (
<div
className={`transition-wrapper ${isAnimating ? 'visible' : 'hidden'}`}
style={{
opacity: isAnimating ? 1 : 0,
transform: isAnimating ? 'translateY(0)' : 'translateY(-20px)',
transition: 'all 300ms ease-in-out'
}}
>
{children}
</div>
);
}

方案五:路由转场动画

import { useLocation } from 'react-router-dom';
import { CSSTransition, TransitionGroup } from 'react-transition-group';

function AnimatedRoutes() {
const location = useLocation();

return (
<TransitionGroup>
<CSSTransition
key={location.key}
classNames="page"
timeout={300}
>
<Routes location={location}>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</CSSTransition>
</TransitionGroup>
);
}

页面转场 CSS:

.page-enter {
opacity: 0;
transform: translateX(100%);
}

.page-enter-active {
opacity: 1;
transform: translateX(0);
transition: all 300ms ease-out;
}

.page-exit {
opacity: 1;
transform: translateX(0);
}

.page-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 300ms ease-in;
}

性能优化技巧

// 使用 transform 和 opacity,避免触发重排
const optimizedStyles = {
transition: 'transform 300ms ease-out, opacity 300ms ease-out',
willChange: 'transform, opacity', // 提示浏览器优化
};

// 使用 requestAnimationFrame 确保动画流畅
function smoothTransition() {
requestAnimationFrame(() => {
// 动画逻辑
});
}

// 减少重渲染
const MemoizedComponent = React.memo(({ isVisible }) => {
return (
<motion.div
animate={{ opacity: isVisible ? 1 : 0 }}
transition={{ duration: 0.3 }}
>
Content
</motion.div>
);
});

答案解析

该题考察如下知识点:

  1. CSS 动画原理:理解 transition、transform、opacity 等 CSS 属性
  2. React 状态管理:控制动画的触发和状态变化
  3. 组件生命周期:在适当的时机触发动画效果
  4. 第三方库使用:熟悉 react-transition-group、Framer Motion 等
  5. 性能优化:避免重排重绘,使用 GPU 加速

不同方案对比

方案优点缺点适用场景
CSS Transitions轻量级,性能好功能有限简单的显示隐藏动画
react-transition-groupAPI 丰富,社区成熟体积较大复杂的列表动画
Framer Motion功能强大,易用体积大,学习成本高复杂的交互动画
自定义 Hook灵活可控开发成本高特定需求的动画

最佳实践

  • 优先使用 transformopacity 实现动画
  • 合理使用 will-change 属性
  • 避免在动画过程中触发重排
  • 考虑用户的动画偏好设置(prefers-reduced-motion)

延伸阅读