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
相关产品推荐
相关产品推荐

