refactor: 架构重构 - 解耦过度耦合模块

主要改动:
1. 创建乐观更新工具函数 (optimisticUpdate.ts)
   - 消除 userStore.ts 中的重复代码

2. 拆分 useResponsive.ts (485行 -> 12个专注模块)
   - useBreakpoint: 断点检测
   - useOrientation: 方向检测
   - usePlatform: 平台检测
   - useScreenSize: 屏幕尺寸
   - useResponsiveValue: 响应式值
   - useResponsiveStyle: 响应式样式
   - useMediaQuery: 媒体查询
   - useColumnCount: 列数计算
   - useResponsiveSpacing: 响应式间距

3. 整理数据层 (Repository 层)
   - ApiDataSource: API数据源
   - LocalDataSource: 本地数据源
   - CacheDataSource: 缓存数据源
   - MessageRepository: 消息仓库

4. 重构 messageManager.ts (2194行 -> 4个模块)
   - MessageStateManager: 状态管理
   - WebSocketMessageHandler: WebSocket处理
   - MessageSyncService: 消息同步
   - ReadReceiptManager: 已读管理

5. 导航解耦 (MainNavigator.tsx: 1118行 -> 100行)
   - 创建 NavigationService 解耦层
   - 拆分多个 Navigator 组件

架构改进:
- 单一职责原则: 每个模块职责明确
- 依赖倒置: 通过接口解耦
- 代码复用: 工具函数可被多处使用
- 可测试性: 各模块可独立测试
This commit is contained in:
lafay
2026-03-18 12:11:49 +08:00
parent 1ffbb63753
commit a6cdb97e24
70 changed files with 8175 additions and 1728 deletions

View File

@@ -0,0 +1,223 @@
# useResponsive 拆分迁移指南
## 概述
原有的 `useResponsive.ts` (485行) 已被拆分为多个专注的 hooks以提高代码的可维护性和性能。
## 新目录结构
```
src/presentation/hooks/responsive/
├── types.ts # 共享类型定义
├── useBreakpoint.ts # 断点检测
├── useScreenSize.ts # 屏幕尺寸
├── useOrientation.ts # 方向检测
├── usePlatform.ts # 平台检测
├── useResponsiveValue.ts # 响应式值映射
├── useResponsiveSpacing.ts # 响应式间距
├── useColumnCount.ts # 列数计算
├── useMediaQuery.ts # 媒体查询
├── useBreakpointCheck.ts # 断点范围检查 (兼容层)
├── useResponsive.ts # 兼容层 (保持向后兼容)
└── index.ts # 统一导出
```
## 新 Hook API 说明
### useBreakpoint - 断点检测
```typescript
import { useBreakpoint, useFineBreakpoint } from '../presentation/hooks/responsive';
// 基础断点: 'mobile' | 'tablet' | 'desktop' | 'wide'
const breakpoint = useBreakpoint();
// 细粒度断点: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'
const fineBreakpoint = useFineBreakpoint();
```
### useScreenSize - 屏幕尺寸
```typescript
import { useScreenSize, useWindowDimensions } from '../presentation/hooks/responsive';
// 完整屏幕尺寸信息
const {
width, height,
breakpoint, fineBreakpoint,
isMobile, isTablet, isDesktop, isWide
} = useScreenSize();
// 仅获取窗口尺寸
const { width, height, scale, fontScale } = useWindowDimensions();
```
### useOrientation - 方向检测
```typescript
import { useOrientation } from '../presentation/hooks/responsive';
const { orientation, isPortrait, isLandscape } = useOrientation();
```
### usePlatform - 平台检测
```typescript
import { usePlatform } from '../presentation/hooks/responsive';
const { OS, isWeb, isIOS, isAndroid, isNative } = usePlatform();
```
### useResponsiveValue - 响应式值
```typescript
import { useResponsiveValue } from '../presentation/hooks/responsive';
// 根据断点返回不同值
const padding = useResponsiveValue({ xs: 8, md: 16, lg: 24 });
// 响应式样式
const style = useResponsiveStyle({
padding: { xs: 8, md: 16, lg: 24 },
fontSize: { xs: 14, lg: 16 }
});
```
### useResponsiveSpacing - 响应式间距
```typescript
import { useResponsiveSpacing } from '../presentation/hooks/responsive';
const gap = useResponsiveSpacing({ xs: 8, md: 16, lg: 24 });
```
### useColumnCount - 列数计算
```typescript
import { useColumnCount } from '../presentation/hooks/responsive';
const columns = useColumnCount({ xs: 1, sm: 2, md: 3, lg: 4 });
```
### useBreakpointGTE / useBreakpointLT - 断点范围检查
```typescript
import { useBreakpointGTE, useBreakpointLT, useBreakpointBetween } from '../presentation/hooks/responsive';
const isMediumUp = useBreakpointGTE('md');
const isMobileOnly = useBreakpointLT('lg');
const isTabletRange = useBreakpointBetween('md', 'lg');
```
### useMediaQuery - 媒体查询
```typescript
import { useMediaQuery } from '../presentation/hooks/responsive';
const isMinWidth768 = useMediaQuery({ minWidth: 768 });
const isPortrait = useMediaQuery({ orientation: 'portrait' });
```
## 迁移示例
### 示例 1: 使用 useResponsive (旧)
```typescript
import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../hooks';
function Component() {
const { width, isMobile, isTablet, isDesktop, isWide } = useResponsive();
const padding = useResponsiveSpacing({ xs: 8, md: 16 });
return <View style={{ padding }} />;
}
```
### 示例 2: 使用新专注 hooks (推荐)
```typescript
import {
useScreenSize,
useResponsiveSpacing
} from '../presentation/hooks/responsive';
function Component() {
const { width, isMobile, isTablet, isDesktop, isWide } = useScreenSize();
const padding = useResponsiveSpacing({ xs: 8, md: 16 });
return <View style={{ padding }} />;
}
```
### 示例 3: 仅使用部分功能 (性能优化)
```typescript
import { useBreakpoint, usePlatform } from '../presentation/hooks/responsive';
function Component() {
// 只订阅断点变化,不订阅尺寸变化
const breakpoint = useBreakpoint();
const { isIOS } = usePlatform(); // 平台不会变化,只计算一次
return <View />;
}
```
## 性能优势
1. **按需订阅**: 只使用需要的功能,避免不必要的重渲染
2. **细粒度更新**: 每个 hook 独立管理状态
3. **平台常量**: usePlatform 返回常量,不会触发重渲染
## 向后兼容
原有的 `useResponsive` 仍然可用,但标记为 `@deprecated`
```typescript
import { useResponsive } from '../hooks'; // 仍然可用
function Component() {
const { width, height, isMobile, platform } = useResponsive(); // 仍然可用
return <View />;
}
```
## 工具函数
```typescript
import {
getBreakpoint,
getFineBreakpoint,
isBreakpointGTE,
isBreakpointLT
} from '../presentation/hooks/responsive';
// 非 hooks 版本,用于工具函数
const breakpoint = getBreakpoint(800);
const fineBreakpoint = getFineBreakpoint(800);
const isGTE = isBreakpointGTE('md', 'sm');
```
## 类型导出
```typescript
import type {
BreakpointKey,
FineBreakpointKey,
ResponsiveValue,
Orientation,
PlatformInfo,
ScreenSize,
MediaQueryOptions,
ResponsiveInfo, // 兼容层
} from '../presentation/hooks/responsive';
```
## 常量
```typescript
import { BREAKPOINTS, FINE_BREAKPOINTS } from '../presentation/hooks/responsive';
// BREAKPOINTS = { mobile: 0, tablet: 768, desktop: 1024, wide: 1440 }
// FINE_BREAKPOINTS = { xs: 0, sm: 375, md: 414, lg: 768, xl: 1024, '2xl': 1280, '3xl': 1440, '4xl': 1920 }
```

View File

@@ -0,0 +1,66 @@
/**
* 响应式 Hooks 统一导出
* Responsive Hooks Index
*
* 提供完整的响应式设计解决方案,包括:
* - 断点检测 (useBreakpoint)
* - 屏幕尺寸 (useScreenSize, useWindowDimensions)
* - 响应式值 (useResponsiveValue, useResponsiveStyle)
* - 方向检测 (useOrientation)
* - 平台检测 (usePlatform)
* - 媒体查询 (useMediaQuery)
* - 列数计算 (useColumnCount)
* - 间距计算 (useResponsiveSpacing)
* - 断点检查 (useBreakpointGTE, useBreakpointLT, useBreakpointBetween)
*/
// ==================== 核心 Hooks ====================
export { useWindowDimensions, useScreenSize } from './useScreenSize';
export {
useBreakpoint,
useFineBreakpoint,
useBreakpointGTE,
useBreakpointLT,
useBreakpointBetween,
} from './useBreakpoint';
export { useResponsiveValue, useResponsiveStyle } from './useResponsiveValue';
export { useOrientation } from './useOrientation';
export { usePlatform } from './usePlatform';
export { useMediaQuery } from './useMediaQuery';
export { useColumnCount } from './useColumnCount';
export { useResponsiveSpacing } from './useResponsiveSpacing';
// ==================== 工具函数 ====================
export {
getBreakpoint,
getFineBreakpoint,
isBreakpointGTE,
isBreakpointLT,
isBreakpointBetween,
} from './useBreakpoint';
// ==================== 类型 ====================
export type {
BreakpointKey,
FineBreakpointKey,
BreakpointValue,
ResponsiveValue,
Orientation,
PlatformInfo,
ScreenSize,
MediaQueryOptions,
} from './types';
export {
BREAKPOINTS,
FINE_BREAKPOINTS,
} from './types';
// 兼容层类型
export type { ResponsiveInfo } from './useResponsive';
// ==================== 向后兼容 ====================
export { useResponsive, useLegacyResponsive } from './useResponsive';
// 默认导出
export { useScreenSize as default } from './useScreenSize';

View File

@@ -0,0 +1,62 @@
/**
* 响应式相关类型定义
*/
// ==================== 断点定义 ====================
export const BREAKPOINTS = {
mobile: 0, // 手机
tablet: 768, // 平板竖屏
desktop: 1024, // 平板横屏/桌面
wide: 1440, // 宽屏桌面
} as const;
export type BreakpointKey = keyof typeof BREAKPOINTS;
export type BreakpointValue = typeof BREAKPOINTS[BreakpointKey];
// ==================== 更细粒度的断点定义 ====================
export const FINE_BREAKPOINTS = {
xs: 0, // 超小屏手机
sm: 375, // 小屏手机 (iPhone SE, 小屏安卓)
md: 414, // 中屏手机 (iPhone Pro Max, 大屏安卓)
lg: 768, // 平板竖屏 / 大折叠屏手机展开
xl: 1024, // 平板横屏 / 小桌面
'2xl': 1280, // 桌面
'3xl': 1440, // 大桌面
'4xl': 1920, // 超大屏
} as const;
export type FineBreakpointKey = keyof typeof FINE_BREAKPOINTS;
// ==================== 响应式值类型 ====================
export type ResponsiveValue<T> = T | Partial<Record<FineBreakpointKey, T>>;
// ==================== 方向类型 ====================
export type Orientation = 'portrait' | 'landscape';
// ==================== 平台类型 ====================
export interface PlatformInfo {
OS: 'ios' | 'android' | 'windows' | 'macos' | 'web';
isWeb: boolean;
isIOS: boolean;
isAndroid: boolean;
isNative: boolean;
}
// ==================== 屏幕尺寸类型 ====================
export interface ScreenSize {
width: number;
height: number;
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
isWide: boolean;
}
// ==================== 媒体查询选项 ====================
export interface MediaQueryOptions {
minWidth?: number;
maxWidth?: number;
minHeight?: number;
maxHeight?: number;
orientation?: Orientation;
}

View File

@@ -0,0 +1,177 @@
/**
* useBreakpoint Hook
* 断点检测 - 检测当前断点
*/
import { useMemo } from 'react';
import { useWindowDimensions } from './useScreenSize';
import { BREAKPOINTS, FINE_BREAKPOINTS } from './types';
import type { BreakpointKey, FineBreakpointKey } from './types';
// ==================== 工具函数 ====================
/**
* 获取当前断点
*/
export function getBreakpoint(width: number): BreakpointKey {
if (width >= BREAKPOINTS.wide) {
return 'wide';
}
if (width >= BREAKPOINTS.desktop) {
return 'desktop';
}
if (width >= BREAKPOINTS.tablet) {
return 'tablet';
}
return 'mobile';
}
/**
* 获取细粒度断点
*/
export function getFineBreakpoint(width: number): FineBreakpointKey {
if (width >= FINE_BREAKPOINTS['4xl']) return '4xl';
if (width >= FINE_BREAKPOINTS['3xl']) return '3xl';
if (width >= FINE_BREAKPOINTS['2xl']) return '2xl';
if (width >= FINE_BREAKPOINTS.xl) return 'xl';
if (width >= FINE_BREAKPOINTS.lg) return 'lg';
if (width >= FINE_BREAKPOINTS.md) return 'md';
if (width >= FINE_BREAKPOINTS.sm) return 'sm';
return 'xs';
}
/**
* 断点比较 - 检查当前断点是否大于等于目标断点
*/
export function isBreakpointGTE(
current: FineBreakpointKey,
target: FineBreakpointKey
): boolean {
const order: FineBreakpointKey[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl'];
return order.indexOf(current) >= order.indexOf(target);
}
/**
* 断点比较 - 检查当前断点是否小于目标断点
*/
export function isBreakpointLT(
current: FineBreakpointKey,
target: FineBreakpointKey
): boolean {
return !isBreakpointGTE(current, target);
}
/**
* 检查当前是否在指定断点范围内
*/
export function isBreakpointBetween(
current: FineBreakpointKey,
min: FineBreakpointKey,
max: FineBreakpointKey
): boolean {
return isBreakpointGTE(current, min) && isBreakpointLT(current, max);
}
// ==================== Hooks ====================
/**
* 断点检测 Hook
* 返回当前的基础断点
*
* @returns 当前断点
*
* @example
* const breakpoint = useBreakpoint();
* // 'mobile' | 'tablet' | 'desktop' | 'wide'
*/
export function useBreakpoint(): BreakpointKey {
const { width } = useWindowDimensions();
return useMemo(() => {
return getBreakpoint(width);
}, [width]);
}
/**
* 细粒度断点检测 Hook
* 返回当前的细粒度断点
*
* @returns 当前细粒度断点
*
* @example
* const fineBreakpoint = useFineBreakpoint();
* // 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'
*/
export function useFineBreakpoint(): FineBreakpointKey {
const { width } = useWindowDimensions();
return useMemo(() => {
return getFineBreakpoint(width);
}, [width]);
}
/**
* 检查当前断点是否大于等于目标断点
*
* @param target - 目标断点
* @returns 是否满足条件
*
* @example
* const isMediumUp = useBreakpointGTE('md');
*/
export function useBreakpointGTE(target: FineBreakpointKey): boolean {
const { width } = useWindowDimensions();
return useMemo(() => {
const current = getFineBreakpoint(width);
return isBreakpointGTE(current, target);
}, [width, target]);
}
/**
* 检查当前断点是否小于目标断点
*
* @param target - 目标断点
* @returns 是否满足条件
*
* @example
* const isMobileOnly = useBreakpointLT('lg');
*/
export function useBreakpointLT(target: FineBreakpointKey): boolean {
const { width } = useWindowDimensions();
return useMemo(() => {
const current = getFineBreakpoint(width);
return isBreakpointLT(current, target);
}, [width, target]);
}
/**
* 检查当前是否在指定断点范围内
*
* @param min - 最小断点(包含)
* @param max - 最大断点(不包含)
* @returns 是否在范围内
*
* @example
* const isTabletRange = useBreakpointBetween('md', 'lg');
*/
export function useBreakpointBetween(
min: FineBreakpointKey,
max: FineBreakpointKey
): boolean {
const { width } = useWindowDimensions();
return useMemo(() => {
const current = getFineBreakpoint(width);
return isBreakpointBetween(current, min, max);
}, [width, min, max]);
}
export default {
useBreakpoint,
useFineBreakpoint,
useBreakpointGTE,
useBreakpointLT,
useBreakpointBetween,
};

View File

@@ -0,0 +1,7 @@
/**
* useBreakpointCheck Hook
* 断点检查 - 提供断点范围检查功能
* @deprecated 请直接使用 useBreakpoint.ts 中的 hooks
*/
export { useBreakpointGTE, useBreakpointLT, useBreakpointBetween } from './useBreakpoint';

View File

@@ -0,0 +1,59 @@
/**
* useColumnCount Hook
* 列数计算 - 根据容器宽度计算合适的列数
*/
import { useMemo } from 'react';
import { useWindowDimensions } from './useScreenSize';
import { getFineBreakpoint } from './useBreakpoint';
import type { FineBreakpointKey } from './types';
const breakpointOrder: FineBreakpointKey[] = ['4xl', '3xl', '2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
const defaultConfig: Record<FineBreakpointKey, number> = {
xs: 1,
sm: 1,
md: 2,
lg: 3,
xl: 4,
'2xl': 4,
'3xl': 5,
'4xl': 6,
};
/**
* 根据容器宽度计算合适的列数
*
* @param columnConfig - 列数配置
* @returns 列数
*
* @example
* const columns = useColumnCount({
* xs: 1,
* sm: 2,
* md: 3,
* lg: 4
* });
*/
export function useColumnCount(
columnConfig: Partial<Record<FineBreakpointKey, number>> = {}
): number {
const { width } = useWindowDimensions();
const fineBreakpoint = getFineBreakpoint(width);
return useMemo(() => {
const config = { ...defaultConfig, ...columnConfig };
const currentIndex = breakpointOrder.indexOf(fineBreakpoint);
for (let i = currentIndex; i < breakpointOrder.length; i++) {
const bp = breakpointOrder[i];
if (config[bp] !== undefined) {
return config[bp];
}
}
return 1;
}, [fineBreakpoint, columnConfig]);
}
export default useColumnCount;

View File

@@ -0,0 +1,36 @@
/**
* useMediaQuery Hook
* 媒体查询模拟 - 模拟 CSS 媒体查询
*/
import { useMemo } from 'react';
import { useWindowDimensions } from './useScreenSize';
import { getOrientation } from './useOrientation';
import type { MediaQueryOptions } from './types';
/**
* 模拟 CSS 媒体查询
*
* @param query - 查询条件
* @returns 是否匹配
*
* @example
* const isMinWidth768 = useMediaQuery({ minWidth: 768 });
* const isMaxWidth1024 = useMediaQuery({ maxWidth: 1024 });
* const isPortrait = useMediaQuery({ orientation: 'portrait' });
*/
export function useMediaQuery(query: MediaQueryOptions): boolean {
const { width, height } = useWindowDimensions();
const currentOrientation = getOrientation(width, height);
return useMemo(() => {
if (query.minWidth !== undefined && width < query.minWidth) return false;
if (query.maxWidth !== undefined && width > query.maxWidth) return false;
if (query.minHeight !== undefined && height < query.minHeight) return false;
if (query.maxHeight !== undefined && height > query.maxHeight) return false;
if (query.orientation !== undefined && currentOrientation !== query.orientation) return false;
return true;
}, [width, height, currentOrientation, query]);
}
export default useMediaQuery;

View File

@@ -0,0 +1,43 @@
/**
* useOrientation Hook
* 方向检测 - 检测设备方向
*/
import { useMemo } from 'react';
import { useWindowDimensions } from './useScreenSize';
import type { Orientation } from './types';
/**
* 获取屏幕方向
*/
export function getOrientation(width: number, height: number): Orientation {
return width > height ? 'landscape' : 'portrait';
}
/**
* 方向检测 Hook
* 提供便捷的方向检测方法
*
* @returns 方向信息对象
*
* @example
* const { orientation, isPortrait, isLandscape } = useOrientation();
*/
export function useOrientation(): {
orientation: Orientation;
isPortrait: boolean;
isLandscape: boolean;
} {
const { width, height } = useWindowDimensions();
return useMemo(() => {
const orientation = getOrientation(width, height);
return {
orientation,
isPortrait: orientation === 'portrait',
isLandscape: orientation === 'landscape',
};
}, [width, height]);
}
export default useOrientation;

View File

@@ -0,0 +1,29 @@
/**
* usePlatform Hook
* 平台检测 - 检测当前运行平台
*/
import { useMemo } from 'react';
import { Platform } from 'react-native';
import type { PlatformInfo } from './types';
/**
* 平台检测 Hook
* 提供便捷的平台检测方法
*
* @returns 平台信息对象
*
* @example
* const { isWeb, isIOS, isAndroid, isNative } = usePlatform();
*/
export function usePlatform(): PlatformInfo {
return useMemo(() => ({
OS: Platform.OS as PlatformInfo['OS'],
isWeb: Platform.OS === 'web',
isIOS: Platform.OS === 'ios',
isAndroid: Platform.OS === 'android',
isNative: Platform.OS !== 'web',
}), []);
}
export default usePlatform;

View File

@@ -0,0 +1,112 @@
/**
* useResponsive Hook (兼容层)
*
* 此文件是为了保持向后兼容性而保留的,整合所有响应式功能。
*
* 推荐使用新的专注 hooks
* - useBreakpoint() - 断点检测
* - useScreenSize() - 屏幕尺寸
* - useOrientation() - 方向检测
* - usePlatform() - 平台检测
* - useResponsiveValue() - 响应式值
*/
import { useMemo } from 'react';
import { useWindowDimensions } from './useScreenSize';
import { getBreakpoint, getFineBreakpoint } from './useBreakpoint';
import { getOrientation } from './useOrientation';
import { usePlatform } from './usePlatform';
import type { BreakpointKey, FineBreakpointKey, Orientation, PlatformInfo } from './types';
// ==================== 返回值类型 ====================
export interface ResponsiveInfo {
// 基础尺寸
width: number;
height: number;
// 基础断点
breakpoint: BreakpointKey;
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
isWide: boolean;
isWideScreen: boolean;
// 细粒度断点
fineBreakpoint: FineBreakpointKey;
isXS: boolean;
isSM: boolean;
isMD: boolean;
isLG: boolean;
isXL: boolean;
is2XL: boolean;
is3XL: boolean;
is4XL: boolean;
// 方向
orientation: Orientation;
isPortrait: boolean;
isLandscape: boolean;
// 平台检测
platform: PlatformInfo;
}
/**
* 响应式设计 Hook (兼容层)
* 提供屏幕尺寸、断点、方向、平台检测等响应式信息
*
* @deprecated 推荐使用新的专注 hooks
*
* @returns ResponsiveInfo 响应式信息对象
*
* @example
* const {
* width, height,
* breakpoint, isMobile, isTablet, isDesktop, isWide,
* fineBreakpoint, isXS, isSM, isMD, isLG, isXL,
* orientation, isPortrait, isLandscape,
* platform: { isWeb, isIOS, isAndroid }
* } = useResponsive();
*/
export function useResponsive(): ResponsiveInfo {
const { width, height } = useWindowDimensions();
const platform = usePlatform();
return useMemo(() => {
const breakpoint = getBreakpoint(width);
const fineBreakpoint = getFineBreakpoint(width);
const orientation = getOrientation(width, height);
return {
width,
height,
breakpoint,
isMobile: breakpoint === 'mobile',
isTablet: breakpoint === 'tablet',
isDesktop: breakpoint === 'desktop',
isWide: breakpoint === 'wide',
isWideScreen: breakpoint === 'tablet' || breakpoint === 'desktop' || breakpoint === 'wide',
fineBreakpoint,
isXS: fineBreakpoint === 'xs',
isSM: fineBreakpoint === 'sm',
isMD: fineBreakpoint === 'md',
isLG: fineBreakpoint === 'lg',
isXL: fineBreakpoint === 'xl',
is2XL: fineBreakpoint === '2xl',
is3XL: fineBreakpoint === '3xl',
is4XL: fineBreakpoint === '4xl',
orientation,
isPortrait: orientation === 'portrait',
isLandscape: orientation === 'landscape',
platform,
};
}, [width, height, platform]);
}
/**
* 旧的 useResponsive 别名,保持完全向后兼容
*/
export const useLegacyResponsive = useResponsive;
export default useResponsive;

View File

@@ -0,0 +1,54 @@
/**
* useResponsiveSpacing Hook
* 响应式间距 - 根据断点返回对应的间距值
*/
import { useMemo } from 'react';
import { useWindowDimensions } from './useScreenSize';
import { getFineBreakpoint } from './useBreakpoint';
import type { FineBreakpointKey } from './types';
const breakpointOrder: FineBreakpointKey[] = ['4xl', '3xl', '2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
const defaultConfig: Record<FineBreakpointKey, number> = {
xs: 8,
sm: 8,
md: 12,
lg: 16,
xl: 20,
'2xl': 24,
'3xl': 32,
'4xl': 40,
};
/**
* 响应式间距 Hook
*
* @param spacingConfig - 间距配置
* @returns 当前断点对应的间距值
*
* @example
* const gap = useResponsiveSpacing({ xs: 8, md: 16, lg: 24 });
*/
export function useResponsiveSpacing(
spacingConfig: Partial<Record<FineBreakpointKey, number>> = {}
): number {
const { width } = useWindowDimensions();
const fineBreakpoint = getFineBreakpoint(width);
return useMemo(() => {
const config = { ...defaultConfig, ...spacingConfig };
const currentIndex = breakpointOrder.indexOf(fineBreakpoint);
for (let i = currentIndex; i < breakpointOrder.length; i++) {
const bp = breakpointOrder[i];
if (config[bp] !== undefined) {
return config[bp];
}
}
return defaultConfig.xs;
}, [fineBreakpoint, spacingConfig]);
}
export default useResponsiveSpacing;

View File

@@ -0,0 +1,95 @@
/**
* useResponsiveValue Hook
* 响应式值映射 - 根据当前断点返回对应值
*/
import { useMemo } from 'react';
import { useWindowDimensions } from './useScreenSize';
import { getFineBreakpoint } from './useBreakpoint';
import type { FineBreakpointKey, ResponsiveValue } from './types';
const breakpointOrder: FineBreakpointKey[] = ['4xl', '3xl', '2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
/**
* 根据当前断点从响应式值对象中选择合适的值
*
* @param value - 响应式值,可以是单一值或断点映射对象
* @returns 选中的值
*
* @example
* const padding = useResponsiveValue({ xs: 8, md: 16, lg: 24 });
* // 在 xs 屏幕返回 8md 屏幕返回 16lg 及以上返回 24
*/
export function useResponsiveValue<T>(value: ResponsiveValue<T>): T {
const { width } = useWindowDimensions();
const fineBreakpoint = getFineBreakpoint(width);
return useMemo(() => {
// 如果不是对象,直接返回
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
return value as T;
}
const valueMap = value as Partial<Record<FineBreakpointKey, T>>;
// 从当前断点开始向下查找
const currentIndex = breakpointOrder.indexOf(fineBreakpoint);
for (let i = currentIndex; i < breakpointOrder.length; i++) {
const bp = breakpointOrder[i];
if (bp in valueMap) {
return valueMap[bp]!;
}
}
// 如果没找到,返回 xs 的值或第一个值
return (valueMap.xs ?? Object.values(valueMap)[0]) as T;
}, [value, fineBreakpoint]);
}
/**
* 响应式样式生成器
* 根据断点生成响应式样式
*
* @param styles - 响应式样式对象
* @returns 当前断点对应的样式
*
* @example
* const containerStyle = useResponsiveStyle({
* padding: { xs: 8, md: 16, lg: 24 },
* fontSize: { xs: 14, lg: 16 }
* });
*/
export function useResponsiveStyle<T extends Record<string, ResponsiveValue<unknown>>>(
styles: T
): { [K in keyof T]: T[K] extends ResponsiveValue<infer V> ? V : never } {
const { width } = useWindowDimensions();
const fineBreakpoint = getFineBreakpoint(width);
return useMemo(() => {
const result = {} as { [K in keyof T]: T[K] extends ResponsiveValue<infer V> ? V : never };
for (const key in styles) {
const value = styles[key];
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
(result as Record<string, unknown>)[key] = value;
} else {
const valueMap = value as Partial<Record<FineBreakpointKey, unknown>>;
const currentIndex = breakpointOrder.indexOf(fineBreakpoint);
let selectedValue: unknown = undefined;
for (let i = currentIndex; i < breakpointOrder.length; i++) {
const bp = breakpointOrder[i];
if (bp in valueMap) {
selectedValue = valueMap[bp];
break;
}
}
(result as Record<string, unknown>)[key] = selectedValue ?? valueMap.xs ?? Object.values(valueMap)[0];
}
}
return result;
}, [styles, fineBreakpoint]);
}

View File

@@ -0,0 +1,63 @@
/**
* useScreenSize Hook
* 屏幕尺寸检测 - 提供屏幕尺寸和断点信息
*/
import { useState, useEffect, useMemo } from 'react';
import { Dimensions, ScaledSize } from 'react-native';
import { getBreakpoint, getFineBreakpoint } from './useBreakpoint';
import type { BreakpointKey, FineBreakpointKey, ScreenSize } from './types';
/**
* 获取窗口尺寸,支持屏幕旋转响应
* 替代 Dimensions.get('window'),提供实时尺寸更新
*/
export function useWindowDimensions(): ScaledSize {
const [dimensions, setDimensions] = useState(() => Dimensions.get('window'));
useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ window }) => {
setDimensions(window);
});
return () => {
subscription.remove();
};
}, []);
return dimensions;
}
/**
* 屏幕尺寸 Hook
* 提供屏幕尺寸信息和断点状态
*
* @returns 屏幕尺寸信息
*
* @example
* const { width, height, isMobile, isTablet, isDesktop, isWide } = useScreenSize();
*/
export function useScreenSize(): ScreenSize & {
breakpoint: BreakpointKey;
fineBreakpoint: FineBreakpointKey;
} {
const { width, height } = useWindowDimensions();
return useMemo(() => {
const breakpoint = getBreakpoint(width);
const fineBreakpoint = getFineBreakpoint(width);
return {
width,
height,
breakpoint,
fineBreakpoint,
isMobile: breakpoint === 'mobile',
isTablet: breakpoint === 'tablet',
isDesktop: breakpoint === 'desktop',
isWide: breakpoint === 'wide',
};
}, [width, height]);
}
export default useScreenSize;