Next.js同时使用React Server Components和namespace导致的命名空间问题

223 阅读2分钟

前言

image.png

这个问题是在较新的Next.js版本(v15+)报的错误,在较老的版本(v14-),你可能会报下述的错误:

image.png 这个问题其实在Next.js的issues里提到了很多次,记录一下
有兴趣的朋友们可以看下Next.js里的issues,他们貌似近期还在排查原因(2025年2月初),例如这一个:Error when using namespace/compound components in React Server Component #75192
本文对此问题产生的根源并无深入的研究,只想解决的朋友们可以直接转到解决方案

现象

本项目技术栈是Next.js,采用的App Router模式,如果采用老的Page Router可能没这问题(不清楚)
具体逻辑不放了,主要就是两点:

  1. 因为用了hooks,所以使用到了use client
  2. 用了一种传统的function assignment pattern方式导出组件namespace (对于Object.assign分配namespace的情况可能也有此问题)

Sidebar.tsx

'use client'
export const SidebarRoot = (...) => {
  ...
};

export const SidebarWrapper = (...) => {
  ...
};

export const SidebarHeader = (...) => {
  ...
};

export const Sidebar = {
  Root: SidebarRoot,
  Wrapper: SidebarWrapper,
  Header: SidebarHeader,
};

layout.tsx

import { Sidebar } from '@/components/ui/Layout';
export default function EditorLayout(...) {
  return (
    ...
      <Sidebar.Root>
        <Sidebar.Header>123</Sidebar.Header>
      </Sidebar.Root>
    ...
  );
}

问题分析

首先要说明,主要问题出在上面这里:

export const Sidebar = { 
  Root: SidebarRoot, 
  Wrapper: SidebarWrapper, 
  Header: SidebarHeader, 
};

我对此问题产生原因的猜测是:和树摇类似,RSC的组件导入难以识别这种类型(将组件直接分配为函数组件属性而非独立导入),Next.js中的optimisers无法将其解析为将使用的组件。
如果您有对这个问题根源的更详细解释或更深入的了解,请务必在文章下评论,我对此非常感兴趣,感谢!

解决方案

  1. 暴力的解决方案:你在使用到该组件的地方,例如这里的layout.tsx,在顶部直接use client。确实没毛病,但是这种解决方法过于粗糙,首先SSR肯定是建议在子节点的地方用use client以尽可能减少性能问题;另外这也没有理解到问题的根源(虽然本文也没有深入了解,目前issues里好像也还在讨论)
  2. 将需要用到use client的组件拉出去单独文件,然后再引入,好像也解决了。但是对于短函数组件,单独拉出去似乎也不够优雅
  3. 直接导出导入独立组件。该方案等于抛弃了namespace
  4. (推荐)另一种namespace导入导出方式,详见下述

这里讲一下第四种方案,其实非常简单:
sidebar.tsx

'use client'
const SidebarRoot = (...) => {
  ...
};

const SidebarWrapper = (...) => {
  ...
};

const SidebarHeader = (...) => {
  ...
};

export { SidebarRoot as Root, SidebarWrapper as Wrapper, SidebarHeader as Header };

layout.tsx

import * as Sidebar from '@/components/ui/Layout';
export default function EditorLayout(...) {
  return (
    ...
      <Sidebar.Root>
        <Sidebar.Header>123</Sidebar.Header>
      </Sidebar.Root>
    ...
  );
}

参考

Multipart Namespace Components: Addressing RSC and Dot Notation Issues