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

React18中state与ref存储组件时调用方式为何存在差异

两种组件调用方式的差异说明

你在异步组件封装中,分别将动态加载的组件存入state和ref,用两种不同方式渲染都能得到结果,相关实现代码如下:

export default function getAsyncComponent(load) {
    const AsyncComponent = () => {
    const [component, setComponent] = useState(null)
    const com = useRef(null)

    const loadComponent = async () => {
      const _component = await load()
      
      com.current = _component.default
      setComponent(_component.default)
    }

    useEffect(() => {
      loadComponent()
    }, [])

    console.log(component, com.current)

    const Com = com.current

    return (
      <div>
        // Both ways are works.
        {component ? component : null}
        {Com ? <Com /> : null}
      </div>
    )
  }

  return <AsyncComponent />
}

控制台打印出两种完全不同的存储值:

// component的打印结果
{$$typeof: Symbol(react.element), type: 'div', key: null, ref: null, props: {…}, …}

// com.current的打印结果
ƒ IndexPage() {
    return ((0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", { children: [(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", { className: _index_less__WEBPACK_I…

该方法的使用场景为React Router 6的路由配置:

function App() {
  return (
      <BrowserRouter>
        <Routes>
          <Route
            path="/"
            element={getAsyncComponent(() => import('src/pages/index'))}
          />
        </Routes>
      </BrowserRouter>
  )
}

依赖版本为react@18.1、react-router-dom@6.3。

从打印结果就能看出两种写法的本质区别:

  • 存入state的component最终变成了已经实例化完成的React元素对象
  • 存入ref的com.current始终是从动态import拿到的原始组件函数引用

两种写法能同时"看起来正常工作"只是表面现象,底层运行逻辑和可靠性差异极大:

1. ref存储 + <Com /> 渲染:符合React规则的正确写法

你在ref里存的始终是未执行的原始组件函数,使用<Com />是标准JSX语法:

  • JSX识别到大写开头的变量会将其作为组件处理,按正常流程调用组件函数、初始化hooks、挂载生命周期、生成React元素插入渲染树
  • 组件状态、上下文、props传递都完全符合React预期,不会出现额外重渲染、重复挂载的问题
  • 这也是所有React组件渲染的标准推荐写法

2. state存储 + 直接插值{component}渲染:存在隐患的错误写法

你把原始组件函数存入state后直接放在插值位置渲染,本身就违反React的渲染规则:

  • 正常情况下,JSX插值位置只接受React元素、字符串、数字、布尔值、null/undefined,如果传入函数React会直接抛出Functions are not valid as a React child的警告,不会正常渲染。你这里能看到渲染结果只是React 18版本下的特殊兼容巧合,后续版本升级后随时可能直接失效。
  • 从你的控制台输出就能看出异常:你存入state的明明是组件函数,打印出来却变成了React元素——这说明渲染流程已经出现了非预期的执行:组件函数被提前意外调用,生成的元素脱离了React的生命周期管控,会导致hooks重复执行、状态丢失、上下文失效、重复挂载等很难排查的问题。
  • 这种写法还可能触发无限重渲染、内存泄漏等隐性bug。

额外说明

你当前实现的异步组件逻辑本身也存在缺陷:

  • 没有处理加载失败、加载中占位的逻辑
  • 没有做加载结果缓存,重复调用会重复执行动态import
  • 针对React Router 6的路由懒加载场景,官方推荐直接使用React.lazy搭配Suspense实现,不需要自己手写封装。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 20:21:38