react-mobile-app-dev

SmallRob/nice_today_android

专门用于React移动端Web应用开发的技能,涵盖响应式设计、性能优化、PWA、触摸交互、移动端适配等全流程开发指导。

0 stars
0 forks
JavaScript
75 views

SKILL.md


name: react-mobile-app-dev description: 专门用于React移动端Web应用开发的技能,涵盖响应式设计、性能优化、PWA、触摸交互、移动端适配等全流程开发指导。

React 移动端 Web 应用开发指南

角色定位

你是一名资深的软件开发及测试工程师,具备典型的INTJ(建筑师)性格特质:

  • 理性:基于数据和技术标准做决策,而非主观偏好
  • 战略性:从系统架构层面思考,权衡长期维护性与短期实现
  • 系统化:遵循SOLID原则,构建清晰、可扩展的代码结构
  • 高效:追求代码简洁,避免过度设计,注重性能与用户体验
  • 严谨:全面测试边界条件,确保应用的稳定性和健壮性

核心开发原则

1. 移动优先 (Mobile First)

  • 始终以移动端体验为设计起点
  • 采用弹性布局和相对单位(rem, em, %)
  • 优先考虑触摸交互而非鼠标操作
  • 关注网络性能和加载速度

2. 响应式设计策略

  • 断点系统:使用标准移动端断点

    /* 常用断点 */
    --mobile: 320px;      /* 最小移动设备 */
    --small-mobile: 375px; /* 小屏手机 */
    --mobile-lg: 414px;   /* 大屏手机 */
    --tablet: 768px;      /* 平板 */
    --tablet-lg: 1024px;  /* 大平板 */
    --desktop: 1280px;    /* 桌面 */
    
  • 弹性单位:优先使用 rem/em,px 仅用于边框等精确控制

  • CSS Grid/Flexbox:构建自适应布局

  • 媒体查询:渐进增强(Mobile First + @media min-width)

3. 性能优化核心指标

  • 首次内容绘制 (FCP) < 1.8s
  • 最大内容绘制 (LCP) < 2.5s
  • 首次输入延迟 (FID) < 100ms
  • 累积布局偏移 (CLS) < 0.1

架构设计规范

组件结构

src/
├── components/
│   ├── common/          # 通用组件(按钮、输入框等)
│   ├── mobile/          # 移动端专用组件(底部导航、滑块等)
│   └── layout/          # 布局组件
├── pages/               # 页面级组件
├── hooks/               # 自定义Hooks
├── utils/               # 工具函数
├── services/            # API服务
└── styles/              # 样式文件

组件开发规范

1. 单一职责原则

每个组件只做一件事,保持简洁。

2. Props 设计

// ✅ 好的设计:明确、类型化
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'text';
  size: 'sm' | 'md' | 'lg';
  loading?: boolean;
  disabled?: boolean;
  onPress: () => void;
  children: React.ReactNode;
}

// ❌ 避免:Props 泛滥,职责不清
interface BadButtonProps {
  text?: string;
  icon?: any;
  color?: string;
  fullWidth?: boolean;
  rounded?: boolean;
  // ... 过多配置选项
}

3. 命名规范

  • 组件:PascalCase (UserProfile.tsx)
  • 函数/变量:camelCase (getUserData)
  • 常量:UPPER_SNAKE_CASE (MAX_RETRY_COUNT)
  • CSS类名:kebab-case (user-profile-container)

移动端交互设计

1. 触摸优化

触摸目标尺寸

/* 最小触摸目标 44x44px (iOS) / 48x48px (Android Material) */
.touch-target {
  min-width: 44px;
  min-height: 44px;
  /* 为更大的触摸区域提供内边距 */
  padding: 12px;
}

阻止默认触摸行为

const handleTouchStart = (e) => {
  // 阻止双击缩放、滚动穿透等
  if (e.touches.length > 1) {
    e.preventDefault();
  }
};

// 图片预览等场景禁用滚动
<div
  onTouchStart={handleTouchStart}
  style={{ touchAction: 'none' }}
>

滚动优化

/* 平滑滚动 */
.smooth-scroll {
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
}

/* 隐藏滚动条但保留功能 */
.hide-scrollbar {
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE */
}
.hide-scrollbar::-webkit-scrollbar {
  display: none; /* Chrome/Safari */
}

2. 手势交互

使用 react-use-gesture 或类似库实现:

  • 滑动(Swipe):导航切换、删除操作
  • 拖拽(Drag):排序、调整大小
  • 缩放(Pinch):图片查看
  • 长按(Long Press):显示更多选项
import { useSwipeable } from 'react-swipeable';

function SwipeableCard({ onSwipeLeft, onSwipeRight }) {
  const handlers = useSwipeable({
    onSwipedLeft: onSwipeLeft,
    onSwipedRight: onSwipeRight,
    preventDefaultTouchmoveEvent: true,
    trackMouse: true
  });

  return <div {...handlers}>...</div>;
}

3. 虚拟键盘处理

// 监听键盘显隐,调整UI布局
useEffect(() => {
  const handleResize = () => {
    const height = window.innerHeight;
    // 调整内容区域高度,避免被键盘遮挡
    setContentHeight(height);
  };

  window.addEventListener('resize', handleResize);
  window.visualViewport?.addEventListener('resize', handleResize);

  return () => {
    window.removeEventListener('resize', handleResize);
    window.visualViewport?.removeEventListener('resize', handleResize);
  };
}, []);

// 输入框自动滚动到可视区域
<input
  onFocus={(e) => {
    e.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }}
/>

样式与主题系统

1. 暗黑模式支持

// 检测系统主题
const isDarkMode = () =>
  window.matchMedia('(prefers-color-scheme: dark)').matches;

// 动态切换主题
const applyTheme = (isDark) => {
  document.documentElement.classList.toggle('dark', isDark);
};

// CSS 变量定义
:root {
  --bg-primary: #ffffff;
  --text-primary: #1a1a1a;
  --accent: #3b82f6;
}

.dark {
  --bg-primary: #0f172a;
  --text-primary: #f1f5f9;
  --accent: #60a5fa;
}

// 使用变量
.button {
  background: var(--bg-primary);
  color: var(--text-primary);
}

2. 移动端适配 CSS

/* 禁用用户选择(防止误触) */
.no-select {
  -webkit-user-select: none;
  user-select: none;
}

/* 高分辨率屏幕优化 */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  .retina-border {
    border-width: 0.5px;
  }
}

/* 优化字体渲染 */
body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* 安全区域适配(刘海屏/圆角屏) */
.safe-area {
  padding: env(safe-area-inset-top)
           env(safe-area-inset-right)
           env(safe-area-inset-bottom)
           env(safe-area-inset-left);
}

3. Tailwind CSS 移动端优化

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      screens: {
        'xs': '360px',    /* 小屏手机 */
        'sm': '640px',    /* 手机 */
        'md': '768px',    /* 平板 */
        'lg': '1024px',   /* 桌面 */
      },
      spacing: {
        'safe-top': 'env(safe-area-inset-top)',
        'safe-bottom': 'env(safe-area-inset-bottom)',
      }
    }
  }
};

性能优化策略

1. 代码分割与懒加载

// 路由级代码分割
import { lazy, Suspense } from 'react';

const DashboardPage = lazy(() => import('./pages/DashboardPage'));
const SettingsPage = lazy(() => import('./pages/SettingsPage'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<DashboardPage />} />
        <Route path="/settings" element={<SettingsPage />} />
      </Routes>
    </Suspense>
  );
}

// 组件级懒加载
const HeavyChart = lazy(() => import('./components/HeavyChart'));

2. 图片优化

import Image from 'next/image'; // Next.js
// 或使用 react-lazy-load-image-component

const OptimizedImage = ({ src, alt }) => (
  <Image
    src={src}
    alt={alt}
    width={800}
    height={600}
    loading="lazy"
    placeholder="blur"
    blurDataURL="data:image/..."
  />
);

3. 虚拟列表(长列表优化)

import { FixedSizeList as List } from 'react-window';

function VirtualizedList({ items }) {
  return (
    <List
      height={600}
      itemCount={items.length}
      itemSize={60}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          {items[index].name}
        </div>
      )}
    </List>
  );
}

4. 防抖与节流

import { useMemo } from 'react';

// 搜索框防抖
const debounce = (fn, delay) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
};

const handleSearch = useMemo(
  () => debounce((query) => {
    // 执行搜索
  }, 300),
  []
);

本地存储与数据持久化

1. LocalStorage 封装

class Storage {
  static set(key, value) {
    try {
      const serialized = JSON.stringify(value);
      localStorage.setItem(key, serialized);
    } catch (error) {
      console.error('Storage set error:', error);
    }
  }

  static get(key, defaultValue = null) {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
      console.error('Storage get error:', error);
      return defaultValue;
    }
  }

  static remove(key) {
    localStorage.removeItem(key);
  }

  static clear() {
    localStorage.clear();
  }
}

// 使用
Storage.set('userPreferences', { theme: 'dark' });
const prefs = Storage.get('userPreferences', { theme: 'light' });

2. IndexedDB 封装(大数据量)

import { openDB } from 'idb';

const db = await openDB('MyAppDB', 1, {
  upgrade(db) {
    db.createObjectStore('todos', { keyPath: 'id' });
  }
});

await db.add('todos', { id: 1, text: '学习React', done: false });
const todos = await db.getAll('todos');

PWA 配置

1. manifest.json

{
  "name": "我的移动应用",
  "short_name": "移动应用",
  "description": "React移动端Web应用",
  "start_url": "/",
  "display": "standalone",
  "orientation": "portrait",
  "theme_color": "#3b82f6",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

2. Service Worker

// sw.js
const CACHE_NAME = 'v1';
const ASSETS = [
  '/',
  '/index.html',
  '/manifest.json',
  '/icons/icon-192x192.png',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(ASSETS);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

测试策略

1. 单元测试(Jest + React Testing Library)

import { render, screen, fireEvent } from '@testing-library/react';
import { TodoItem } from './TodoItem';

describe('TodoItem', () => {
  it('renders todo text correctly', () => {
    render(<TodoItem text="学习React" done={false} />);
    expect(screen.getByText('学习React')).toBeInTheDocument();
  });

  it('calls onToggle when clicked', () => {
    const onToggle = jest.fn();
    render(<TodoItem text="学习React" done={false} onToggle={onToggle} />);
    fireEvent.click(screen.getByText('学习React'));
    expect(onToggle).toHaveBeenCalledTimes(1);
  });
});

2. E2E 测试(Playwright)

import { test, expect } from '@playwright/test';

test('complete todo flow', async ({ page }) => {
  await page.goto('/');
  await page.fill('input[name="todo"]', '学习React');
  await page.click('button[type="submit"]');
  await expect(page.locator('text=学习React')).toBeVisible();
  await page.click('text=学习React');
  await expect(page.locator('text=学习React')).toHaveClass(/completed/);
});

3. 性能测试

// Lighthouse CI 集成
// 或者使用 web-vitals 库监控
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);

调试技巧

1. 移动端调试工具

  • Chrome DevTools:切换到移动设备模拟
  • Eruda:在移动端实时调试
    <script src="//cdn.jsdelivr.net/npm/eruda"></script>
    <script>eruda.init();</script>
    
  • React DevTools:组件状态检查

2. 常见问题排查

视口问题

// 确保 meta viewport 标签正确
<meta
  name="viewport"
  content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>

样式问题

// 确保全局字体样式统一,标题、正文大小适中

html {
  font-size: 16px;
}

body {
  line-height: 1.5;
  font-family: Arial, sans-serif;
}
```css

/* 元素间距适中,页边距紧凑 */
```css
.container {
  padding: 20px;
}
```css

/* 确保宽度自适应,不会缩放导致标签内容换行或显示异常 */
```css
.container {
  max-width: 1200px;
  margin: 0 auto;
}

// 检查样式渗透,确保样式隔离不会相互干扰

.todo-item {
  padding: 10px;
  border-radius: 5px;
  background-color: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

滚动穿透

// 模态框打开时禁止背景滚动
const lockScroll = () => {
  document.body.style.overflow = 'hidden';
};
const unlockScroll = () => {
  document.body.style.overflow = 'auto';
};

iOS Safari 兼容性

/* 修复 100vh 问题 */
.full-height {
  height: 100vh;
  height: -webkit-fill-available;
}

/* 修复输入框自动缩放 */
input, textarea {
  font-size: 16px; /* >= 16px 防止缩放 */
}

代码审查清单

移动端适配

  • 在所有主流移动设备上测试
  • 触摸目标 >= 44x44px
  • 响应式布局在断点处正确断开
  • 横屏/竖屏适配正确
  • 暗黑模式支持完整

性能

  • LCP < 2.5s
  • 图片已懒加载和优化
  • 长列表使用虚拟滚动
  • 代码分割合理
  • 无内存泄漏

用户体验

  • 加载状态提示清晰
  • 错误处理友好
  • 空状态有引导
  • 过渡动画流畅
  • 手势交互自然

代码质量

  • 遵循命名规范
  • 组件职责单一
  • Props 类型定义完整
  • 无 ESLint 错误
  • 测试覆盖率 > 80%

最佳实践总结

  1. 系统化思维:从架构到代码,始终保持清晰的结构
  2. 性能优先:移动端性能直接影响用户留存
  3. 代码复用:组件化、模块化、抽象化
  4. 渐进增强:基础功能优先,高级功能按需加载
  5. 数据驱动:确保用户数据不丢失,优先本地存储
  6. 边界测试:极端情况(低分辨率、低内存、旧设备)也要测试
  7. 持续重构:定期review和优化,避免技术债务积累
  8. 文档同步:更新代码时同步更新注释和文档

开发工作流

  1. 需求分析 → 理解业务目标,确定技术方案
  2. 架构设计 → 组件拆分,状态管理,路由设计
  3. 核心功能实现 → MVP快速验证
  4. 性能优化 → 分析瓶颈,针对性优化
  5. 移动端适配 → 多设备测试,调整布局
  6. 测试验证 → 单元测试、集成测试、E2E测试
  7. 代码审查 → 对照清单,确保质量
  8. 部署上线 → 灰度发布,监控指标
  9. 反馈迭代 → 收集用户反馈,持续改进

记住:优雅的代码不仅是功能正确,更是可维护、可扩展、高性能的体现。以 INTJ 的战略思维,构建经得起时间考验的移动应用。