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

Next.js/React SSR中如何实现类似Spring @Bean的可交换类实例

Next.js/React Server Components 多数据源切换与单例依赖注入方案

1. 先定义统一数据接口

首先把两个数据源要遵循的接口规范定死,确保后续实现逻辑对齐:

// src/data/interfaces.ts
export interface DataSource {
  getUsers(): Promise<User[]>;
  getUserById(id: string): Promise<User | null>;
  // 其他业务需要的统一方法...
}

export interface User {
  id: string;
  name: string;
  email: string;
}

2. 实现两个数据源类

分别对应不同的API实现,完全遵循上面的接口:

// src/data/RemoteDataSource.ts
import { DataSource, User } from './interfaces';

export class RemoteDataSource implements DataSource {
  async getUsers(): Promise<User[]> {
    // 调用真实外部API的逻辑
    const res = await fetch('https://api.your-domain.com/users');
    return res.json();
  }

  async getUserById(id: string): Promise<User | null> {
    const res = await fetch(`https://api.your-domain.com/users/${id}`);
    if (!res.ok) return null;
    return res.json();
  }
}
// src/data/MockDataSource.ts
import { DataSource, User } from './interfaces';

export class MockDataSource implements DataSource {
  private mockUsers: User[] = [
    { id: '1', name: '测试用户1', email: 'test1@example.com' },
    { id: '2', name: '测试用户2', email: 'test2@example.com' },
  ];

  async getUsers(): Promise<User[]> {
    return this.mockUsers;
  }

  async getUserById(id: string): Promise<User | null> {
    return this.mockUsers.find(u => u.id === id) || null;
  }
}

3. 用工厂模式实现启动时切换+单例

通过环境变量控制启动时的数据源选择,同时保证实例是单例(常驻应用内存,不会随请求重建):

// src/data/DataSourceFactory.ts
import { DataSource } from './interfaces';
import { RemoteDataSource } from './RemoteDataSource';
import { MockDataSource } from './MockDataSource';

class DataSourceFactory {
  private static instance: DataSource;

  static getInstance(): DataSource {
    if (!DataSourceFactory.instance) {
      // 读取启动时的环境变量,决定用哪个数据源
      const useMock = process.env.USE_MOCK_DATA === 'true';
      DataSourceFactory.instance = useMock ? new MockDataSource() : new RemoteDataSource();
    }
    return DataSourceFactory.instance;
  }
}

// 直接导出单例实例,业务代码直接用
export const dataSource = DataSourceFactory.getInstance();

启动前配置环境变量即可,比如:USE_MOCK_DATA=true npm run dev,或者在.env文件里写入USE_MOCK_DATA=true。

4. 在Server Components中使用

因为是Server Components,直接导入单例数据源调用就行,实例会一直驻留在服务器内存:

// src/app/users/page.tsx (Server Component)
import { dataSource } from '@/data/DataSourceFactory';

export default async function UsersPage() {
  const users = await dataSource.getUsers();

  return (
    <div>
      <h1>用户列表</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name} ({user.email})</li>
        ))}
      </ul>
    </div>
  );
}

5. 进阶:用React Context实现依赖注入(可选)

如果需要更灵活的替换(比如测试场景),可以用React Context封装,Server Components中直接使用:

// src/data/DataSourceContext.ts
import { createContext, useContext } from 'react';
import { DataSource } from './interfaces';
import { dataSource } from './DataSourceFactory';

export const DataSourceContext = createContext<DataSource>(dataSource);

export function useDataSource() {
  return useContext(DataSourceContext);
}

业务组件中使用方式:

// src/app/user/[id]/page.tsx (Server Component)
import { useDataSource } from '@/data/DataSourceContext';

export default async function UserPage({ params }: { params: { id: string } }) {
  const dataSource = useDataSource();
  const user = await dataSource.getUserById(params.id);

  if (!user) {
    return <div>用户不存在</div>;
  }

  return (
    <div>
      <h1>{user.name}</h1>
      <p>邮箱:{user.email}</p>
    </div>
  );
}

测试时只需要替换Context的默认值,就能快速切换数据源,不用修改业务代码。

关键注意事项

  • 单例生命周期:Node.js环境下的单例会常驻服务器内存,不会随每个请求销毁,完全符合你要的非请求作用域要求。
  • 环境变量注意:如果是服务端专用的环境变量,不需要加NEXT_PUBLIC_前缀,直接在.env或启动命令中配置即可。
  • 避免客户端使用:如果数据源包含服务端敏感逻辑(比如数据库访问、内部API调用),不要在Client Components中直接导入,可封装成API路由让客户端间接调用。

内容的提问来源于stack exchange,提问作者Paul Wellner Bou

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:53:20