Skip to content

前端开发指南

IfAI React/TypeScript 前端开发指南。

快速开始

前置要求

  • Node.js 18+
  • npm 或 pnpm
  • 熟悉 React 19
  • TypeScript 经验

开发设置

bash
# 克隆仓库
git clone https://github.com/peterfei/ifai.git
cd ifai

# 安装依赖
npm install

# 启动开发服务器
npm run tauri:dev

组件开发

组件结构

typescript
// src/components/Example/Example.tsx
import React from 'react'
import { useExampleStore } from '../../stores/useExampleStore'

export const Example: React.FC = () => {
  const { data, actions } = useExampleStore()

  return (
    <div className="example-component">
      {/* 组件 JSX */}
    </div>
  )
}

最佳实践

  1. 使用带 hooks 的函数组件
  2. TypeScript Props 使用接口
  3. 对昂贵的渲染使用记忆化
  4. 使用 TailwindCSS 进行样式设置

示例组件

typescript
// src/components/AIChat/MessageList.tsx
import React, { useMemo } from 'react'
import { useChatStore } from '../../stores/useChatStore'
import { MessageItem } from './MessageItem'

interface MessageListProps {
  className?: string
}

export const MessageList: React.FC<MessageListProps> = ({ className }) => {
  const { messages, isLoading } = useChatStore()

  const sortedMessages = useMemo(
    () => messages.sort((a, b) => a.timestamp - b.timestamp),
    [messages]
  )

  if (isLoading && sortedMessages.length === 0) {
    return <div className="loading">加载中...</div>
  }

  return (
    <div className={`message-list ${className || ''}`}>
      {sortedMessages.map((message) => (
        <MessageItem key={message.id} message={message} />
      ))}
    </div>
  )
}

Zustand 状态管理

Store 模式

typescript
// src/stores/useExampleStore.ts
import { create } from 'zustand'

interface ExampleState {
  data: string[]
  isLoading: boolean
  error: string | null
}

interface ExampleActions {
  fetchData: () => Promise<void>
  clearError: () => void
}

type ExampleStore = ExampleState & ExampleActions

export const useExampleStore = create<ExampleStore>((set, get) => ({
  // 状态
  data: [],
  isLoading: false,
  error: null,

  // 操作
  fetchData: async () => {
    set({ isLoading: true, error: null })
    try {
      const data = await invoke<string[]>('get_data')
      set({ data, isLoading: false })
    } catch (error) {
      set({ error: error.message, isLoading: false })
    }
  },

  clearError: () => set({ error: null })
}))

使用 Store

typescript
// 在组件中
import { useExampleStore } from '../../stores/useExampleStore'

export const MyComponent = () => {
  // 选择特定状态
  const data = useExampleStore((state) => state.data)
  const fetchData = useExampleStore((state) => state.fetchData)

  // 或使用完整 store
  const { data, isLoading, fetchData } = useExampleStore()

  useEffect(() => {
    fetchData()
  }, [fetchData])

  return <div>{data.join(', ')}</div>
}

Tauri IPC 集成

调用命令

typescript
import { invoke } from '@tauri-apps/api/core'

// 简单命令
const result = await invoke('simple_command', { arg1: 'value' })

// 带类型安全
interface CommandArgs {
  path: string
  options?: { recursive: boolean }
}

interface CommandReturn {
  success: boolean
  data: string[]
}

const result = await invoke<CommandReturn>('read_directory', {
  path: '/src',
  options: { recursive: true }
} satisfies CommandArgs)

监听事件

typescript
import { listen } from '@tauri-apps/api/event'

useEffect(() => {
  const unlisten = listen('file-changed', (event) => {
    console.log('文件已更改:', event.payload)
  })

  return () => {
    unlisten.then((fn) => fn())
  }
}, [])

TailwindCSS 样式

工具类

typescript
<div className="flex items-center justify-between p-4 bg-gray-800 rounded-lg">
  <h2 className="text-xl font-bold text-white">标题</h2>
  <button className="px-4 py-2 bg-blue-500 hover:bg-blue-600 rounded">
    点击我
  </button>
</div>

自定义组件

typescript
// 带变体的 Button 组件
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'danger'
}

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  className,
  ...props
}) => {
  const baseClasses = 'px-4 py-2 rounded font-medium transition-colors'
  const variantClasses = {
    primary: 'bg-blue-500 hover:bg-blue-600 text-white',
    secondary: 'bg-gray-500 hover:bg-gray-600 text-white',
    danger: 'bg-red-500 hover:bg-red-600 text-white'
  }

  return (
    <button
      className={`${baseClasses} ${variantClasses[variant]} ${className || ''}`}
      {...props}
    />
  )
}

Monaco 编辑器集成

基本设置

typescript
import { Editor } from '@monaco-editor/react'

export const CodeEditor: React.FC = () => {
  const [code, setCode] = useState('// 您的代码')

  return (
    <Editor
      height="500px"
      language="typescript"
      theme="vs-dark"
      value={code}
      onChange={(value) => setCode(value || '')}
      options={{
        minimap: { enabled: false },
        fontSize: 14,
        lineNumbers: 'on'
      }}
    />
  )
}

测试

Vitest 单元测试

typescript
// Example.test.ts
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Example } from './Example'

describe('Example', () => {
  it('正确渲染', () => {
    render(<Example />)
    expect(screen.getByText('Example')).toBeInTheDocument()
  })
})

运行测试

bash
# 单元测试
npm run test

# 监听模式
npm run test:watch

# 覆盖率
npm run test:coverage

性能优化

记忆化

typescript
import { memo, useMemo, useCallback } from 'react'

// 记忆整个组件
export const ExpensiveComponent = memo(({ data }) => {
  const processedData = useMemo(() => {
    return data.map(/* 昂贵计算 */)
  }, [data])

  const handleClick = useCallback(() => {
    // 处理点击
  }, [])

  return <div onClick={handleClick}>{/* ... */}</div>
})

调试

React DevTools

bash
# 安装 React DevTools 浏览器扩展
# 组件检查和性能分析

Zustand DevTools

typescript
import { devtools } from 'zustand/middleware'

export const useExampleStore = create(
  devtools(
    (set, get) => ({
      // store 实现
    }),
    { name: 'ExampleStore' }
  )
)

下一步

基于 MIT 许可发布