react-key-viewport-causality-analysis.md 3.3 KB

React Key 缺失导致 Viewport 图像残留的因果关系分析

问题:为什么没有 key 会导致图像残留?

React 组件重用机制

// Film.tsx 当前代码(有问题)
const generateCell = (imageId, indexOfCell) => {
  return (<ViewportContainer imageId={imageId} currentFilm={currentFilm} indexOfCell={indexOfCell} />);
};

因果关系链

1. 没有 key → React 重用组件实例

  • 没有 key 时:React 认为相同位置的相同类型组件是同一个组件
  • 切换胶片时
    • 胶片1的第一个格子:<ViewportContainer imageId="image-A" .../>
    • 切换到胶片2的第一个格子:<ViewportContainer imageId="image-B" .../>
    • React 认为这是同一个组件,只需要更新 props

2. 组件重用 → 内部状态不重置

// ViewportContainer 内部状态在组件重用时不会重置
const [hasImage, setHasImage] = useState<boolean>(false);
const [currentImageId, setCurrentImageId] = useState<string | null>(null);
const [stackViewport, setStackViewport] = useState<any>(null);
const viewportRef = useRef(null);

// 🚨 问题:切换胶片时这些状态保持原值!

3. 状态不重置 → Viewport 实例保持原有状态

// useEffect 只在组件首次挂载时执行
useEffect(() => {
  // 创建渲染引擎和 viewport...
  setStackViewport(viewport);
}, []); // 空依赖数组意味着只执行一次

// 🚨 关键问题:viewport 实例仍然绑定着之前的图像数据

4. Viewport 状态保持 → 图像残留显示

  • Cornerstone 的 viewport 实例内部缓存了图像数据
  • 即使 props 中的 imageId 改变,viewport 可能仍显示缓存的图像
  • useEffect([imageId, stackViewport]) 虽然会触发,但 stackViewport 实例本身没变

具体场景演示

场景:胶片1 → 胶片2 切换

1. 用户在胶片1拖拽图像A到第一个格子
   - ViewportContainer 创建 viewport-1
   - viewport-1 显示图像A
   - hasImage = true, currentImageId = "imageA"

2. 用户切换到胶片2(第一个格子为空)
   - React 重用 ViewportContainer 组件实例
   - props 更新:imageId 从 "imageA" 变为 null
   - 但是 viewport-1 实例仍然存在,仍然显示图像A
   - 虽然触发 useEffect,但清空逻辑可能执行不彻底

3. 结果:胶片2显示了胶片1的图像

解决方案:为什么 key 能解决问题?

有 key 的情况

// 修复后的代码
const generateCell = (imageId, indexOfCell) => {
  const uniqueKey = `${currentFilm.id}-${indexOfCell}`;
  return (
    <ViewportContainer 
      key={uniqueKey}  // 🎯 关键修复
      imageId={imageId} 
      currentFilm={currentFilm} 
      indexOfCell={indexOfCell} 
    />
  );
};

修复后的因果关系

1. 切换胶片时,key 从 "1-0" 变为 "2-0"
2. React 识别这是不同的组件,卸载旧组件,创建新组件
3. 旧组件卸载 → 触发 cleanup 函数 → 清理 viewport
4. 新组件挂载 → 重新初始化所有状态 → 创建新的 viewport
5. 结果:完全隔离,没有状态残留

总结

核心问题:React 组件重用 + Cornerstone Viewport 状态持久化 根本原因:没有 key 导致 React 错误地重用了应该重新创建的组件 解决方案:通过 key 强制 React 在胶片切换时重新创建组件实例