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

使用React+TypeScript+Vite时动态导入组件致Fast Refresh失效

问题分析

错误 Cannot destructure property 'currentStep' of 'data' as it is null 表明 useApp() 返回了 null,根源在于上下文(Context)的初始化或传递存在问题,结合HMR场景,可能是热重载时上下文临时失效、状态丢失,或者上下文默认值未正确设置。

解决方案

1. 修复上下文默认值

避免上下文默认值为 null,提供一个安全的默认对象,确保即使未挂载 Provider 也不会抛出解构错误:

// MultiStepContext.tsx
import { createContext, useContext } from 'react';
import useMultiStep from './hooks/useMultiStep';

type MultiStepContextType = ReturnType<typeof useMultiStep>;

// 提供安全默认值
const MultiStepContext = createContext<MultiStepContextType>({
  step: 0,
  next: () => {},
  back: () => {},
  currentStep: null,
  isFirst: true,
});

export const useApp = () => useContext(MultiStepContext);
export default MultiStepContext;

2. 在App组件中添加空值检查

处理上下文值未就绪的情况,避免直接解构 null:

function App() {
  const data = useApp();

  // 处理上下文未就绪或currentStep为空的情况
  if (!data.currentStep) {
    return <div>Loading...</div>;
  }

  const { currentStep, next, back, isFirst } = data;

  // 剩余代码保持不变
}

3. 优化上下文Provider与状态持久化

  • 将上下文Provider包裹在专用组件中,确保Hook调用符合React规则;
  • 使用useMemo缓存上下文值,减少HMR时的不必要重渲染;
  • 新增localStorage持久化,即使页面全量重载也能保留步骤状态:
// AppWrapper.tsx
import { useMemo, useEffect } from 'react';
import { lazy } from 'react';
import MultiStepContext from './contexts/MultiStepContext';
import useMultiStep from './hooks/useMultiStep';
import App from './App';

function AppWrapper() {
  // 缓存步骤数组,避免HMR时频繁创建新引用
  const steps = useMemo(() => [
    {
      id: 0,
      name: 'Personal Info',
      component: lazy(() => import('./components/PersonalInfo')),
    },
    // 其他步骤...
  ], []);

  const dataSteps = useMultiStep(steps);
  
  // 缓存上下文值,仅当step变化时更新
  const contextValue = useMemo(() => dataSteps, [dataSteps.step]);

  return (
    <MultiStepContext.Provider value={contextValue}>
      <App />
    </MultiStepContext.Provider>
  );
}

export default AppWrapper;

修改useMultiStep Hook添加localStorage持久化:

import { useState, useEffect } from 'react';
import { MultistepProps } from '../@types/Multiform';

const useMultiStep = (steps: MultistepProps[]) => {
  // 从localStorage加载初始状态
  const [step, setStep] = useState(() => {
    const saved = localStorage.getItem('multiStep');
    return saved ? parseInt(saved, 10) : 0;
  });

  // 状态变化时保存到localStorage
  useEffect(() => {
    localStorage.setItem('multiStep', step.toString());
  }, [step]);

  if (!steps || steps.length === 0) {
    return {
      step: 0,
      next: () => {},
      back: () => {},
      currentStep: null,
      isFirst: true,
    };
  }

  const isLast = step === steps.length - 1;
  const isFirst = step === 0;

  const next = (): void => {
    if (isLast) return;
    setStep((current) => current + 1);
  };

  const back = (): void => {
    if (isFirst) return;
    setStep((current) => current - 1);
  };

  return {
    step,
    next,
    back,
    currentStep: steps[step] || null,
    isFirst,
  };
};

export default useMultiStep;

4. 确保根组件正确挂载Wrapper

在main.tsx中使用AppWrapper作为根组件:

import React from 'react';
import ReactDOM from 'react-dom/client';
import AppWrapper from './AppWrapper';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <AppWrapper />
  </React.StrictMode>,
);

更优实现思路

  • 步骤配置与组件解耦:将步骤元数据(名称、验证规则等)与懒加载组件分离,便于维护;
  • 使用React Router管理步骤路由:通过URL参数同步当前步骤,支持前进后退导航;
  • 表单状态集中管理:使用useReducer或第三方状态库(如Zustand)统一管理多步骤表单数据,避免上下文层级过深。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 16:07:06