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:
223
src/presentation/hooks/responsive/MIGRATION.md
Normal file
223
src/presentation/hooks/responsive/MIGRATION.md
Normal 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 }
|
||||
```
|
||||
66
src/presentation/hooks/responsive/index.ts
Normal file
66
src/presentation/hooks/responsive/index.ts
Normal 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';
|
||||
62
src/presentation/hooks/responsive/types.ts
Normal file
62
src/presentation/hooks/responsive/types.ts
Normal 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;
|
||||
}
|
||||
177
src/presentation/hooks/responsive/useBreakpoint.ts
Normal file
177
src/presentation/hooks/responsive/useBreakpoint.ts
Normal 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,
|
||||
};
|
||||
7
src/presentation/hooks/responsive/useBreakpointCheck.ts
Normal file
7
src/presentation/hooks/responsive/useBreakpointCheck.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* useBreakpointCheck Hook
|
||||
* 断点检查 - 提供断点范围检查功能
|
||||
* @deprecated 请直接使用 useBreakpoint.ts 中的 hooks
|
||||
*/
|
||||
|
||||
export { useBreakpointGTE, useBreakpointLT, useBreakpointBetween } from './useBreakpoint';
|
||||
59
src/presentation/hooks/responsive/useColumnCount.ts
Normal file
59
src/presentation/hooks/responsive/useColumnCount.ts
Normal 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;
|
||||
36
src/presentation/hooks/responsive/useMediaQuery.ts
Normal file
36
src/presentation/hooks/responsive/useMediaQuery.ts
Normal 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;
|
||||
43
src/presentation/hooks/responsive/useOrientation.ts
Normal file
43
src/presentation/hooks/responsive/useOrientation.ts
Normal 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;
|
||||
29
src/presentation/hooks/responsive/usePlatform.ts
Normal file
29
src/presentation/hooks/responsive/usePlatform.ts
Normal 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;
|
||||
112
src/presentation/hooks/responsive/useResponsive.ts
Normal file
112
src/presentation/hooks/responsive/useResponsive.ts
Normal 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;
|
||||
54
src/presentation/hooks/responsive/useResponsiveSpacing.ts
Normal file
54
src/presentation/hooks/responsive/useResponsiveSpacing.ts
Normal 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;
|
||||
95
src/presentation/hooks/responsive/useResponsiveValue.ts
Normal file
95
src/presentation/hooks/responsive/useResponsiveValue.ts
Normal 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 屏幕返回 8,md 屏幕返回 16,lg 及以上返回 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]);
|
||||
}
|
||||
63
src/presentation/hooks/responsive/useScreenSize.ts
Normal file
63
src/presentation/hooks/responsive/useScreenSize.ts
Normal 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;
|
||||
Reference in New Issue
Block a user