状態管理の詳解
🔄 状態管理アーキテクチャの概要
フロントエンドは Reducer + Context パターンで状態を管理しています。不変な状態更新と型安全な設計を組み合わせ、予測可能で高パフォーマンスな状態管理システムを実現しています。
🏗️ 状態アーキテクチャの設計
状態ツリーの構造
interface AppState {
// 连接状态
connection: {
state: ConnectionState
url: string
error: string | null
reconnectAttempts: number
socket: WebSocket | null
}
// 对话状态
conversation: {
messages: Message[]
currentInput: string
currentTurnId: string | null
isAssistantTyping: boolean
isReceivingStream: boolean
hasToolCallsInCurrentTurn: boolean
}
// UI状态
ui: {
inputMode: InputMode
isListening: boolean
asrResult: string
isInputDisabled: boolean
}
// 系统状态
system: {
statuses: Record<string, SystemStatus>
errors: ErrorInfo[]
lastMessage: WebSocketMessage | null
}
// 工具状态
tools: {
activeCalls: Record<string, ToolCall>
}
}
Reducer パターンの実装
function appStateReducer(state: AppState, action: AppAction): AppState {
switch (action.type) {
// 连接管理
case 'CONNECTION_CONNECTING':
return {
...state,
connection: {
...state.connection,
state: 'connecting',
url: action.payload.url,
error: null
}
}
case 'CONNECTION_CONNECTED':
return {
...state,
connection: {
...state.connection,
state: 'connected',
socket: action.payload.socket,
error: null
}
}
// 消息管理
case 'MESSAGE_ADD':
return {
...state,
conversation: {
...state.conversation,
messages: [...state.conversation.messages, action.payload.message]
}
}
case 'MESSAGE_UPDATE':
return {
...state,
conversation: {
...state.conversation,
messages: state.conversation.messages.map(msg =>
msg.id === action.payload.id
? { ...msg, ...action.payload.updates }
: msg
)
}
}
// 流式响应
case 'STREAM_CHUNK':
return updateStreamContent(state, action.payload)
// 工具调用
case 'TOOL_CALL_START':
return {
...state,
tools: {
...state.tools,
activeCalls: {
...state.tools.activeCalls,
[action.payload.toolCall.id]: action.payload.toolCall
}
},
conversation: {
...state.conversation,
hasToolCallsInCurrentTurn: true
}
}
default:
return state
}
}
🎯 主要機能の実装
メッセージのストリーミング更新
function updateStreamContent(state: AppState, payload: { content: string; isFirst?: boolean }): AppState {
const { content, isFirst } = payload
if (!state.conversation.currentTurnId) {
return state
}
const messageIndex = state.conversation.messages.findIndex(
msg => msg.id === state.conversation.currentTurnId
)
if (messageIndex === -1) {
return state
}
const currentMessage = state.conversation.messages[messageIndex]
const newContent = isFirst ? content : (currentMessage.content || '') + content
const updatedMessages = [...state.conversation.messages]
updatedMessages[messageIndex] = {
...currentMessage,
content: newContent
}
return {
...state,
conversation: {
...state.conversation,
messages: updatedMessages,
isReceivingStream: true
}
}
}
インテリジェントな重複排除の仕組み
// 全局去重状态
declare global {
interface Window {
processedLLMResponses?: Set<string>
}
}
// LLM_FINAL_RESPONSE去重处理
case 'LLM_FINAL_RESPONSE':
const responseContent = message.payload.content || ''
const currentTurnId = stateRef.current.conversation.currentTurnId
const responseKey = `${currentTurnId}_${responseContent.length}_${responseContent.slice(-20)}`
if (!window.processedLLMResponses) {
window.processedLLMResponses = new Set()
}
if (window.processedLLMResponses.has(responseKey)) {
return state // 跳过重复响应
}
window.processedLLMResponses.add(responseKey)
// 继续处理逻辑...
⚡ パフォーマンス最適化の戦略
1. 再レンダリングの削減
// 使用React.memo优化组件
const MessageBubble = React.memo(({ message }: { message: Message }) => {
return <div>{message.content}</div>
}, (prevProps, nextProps) => {
// 自定义比较函数
return (
prevProps.message.id === nextProps.message.id &&
prevProps.message.content === nextProps.message.content &&
prevProps.message.toolCalls?.length === nextProps.message.toolCalls?.length
)
})
// 使用useCallback稳定引用
const handleSendMessage = useCallback(() => {
sendUserInput()
}, [sendUserInput])
2. 状態セレクタの最適化
// 自定义选择器Hook
function useMessages() {
const { state } = useAppState()
return useMemo(() => state.conversation.messages, [state.conversation.messages])
}
function useCurrentInput() {
const { state } = useAppState()
return useMemo(() => state.conversation.currentInput, [state.conversation.currentInput])
}
// 避免整个状态对象的依赖
function useConnectionStatus() {
const { state } = useAppState()
return useMemo(() => ({
isConnected: state.connection.state === 'connected',
error: state.connection.error,
reconnectAttempts: state.connection.reconnectAttempts
}), [state.connection.state, state.connection.error, state.connection.reconnectAttempts])
}
3. 状態の一括更新
// 批量更新多个状态字段
case 'TURN_COMPLETE_BATCH':
return {
...state,
conversation: {
...state.conversation,
isAssistantTyping: false,
isReceivingStream: false,
currentTurnId: null
},
tools: {
...state.tools,
activeCalls: {} // 清空活跃工具调用
}
}
🔒 型安全
Action の型定義
type AppAction =
// 连接相关
| { type: 'CONNECTION_CONNECTING'; payload: { url: string } }
| { type: 'CONNECTION_CONNECTED'; payload: { socket: WebSocket } }
| { type: 'CONNECTION_DISCONNECTED'; payload: {} }
| { type: 'CONNECTION_ERROR'; payload: { error: string } }
// 消息相关
| { type: 'MESSAGE_ADD'; payload: { message: Message } }
| { type: 'MESSAGE_UPDATE'; payload: { id: string; updates: Partial<Message> } }
| { type: 'MESSAGE_CLEAR_ALL'; payload: {} }
// 流式响应
| { type: 'STREAM_START'; payload: { turnId: string } }
| { type: 'STREAM_CHUNK'; payload: { content: string; isFirst?: boolean } }
| { type: 'STREAM_END'; payload: { finalContent?: string } }
// 工具调用
| { type: 'TOOL_CALL_START'; payload: { toolCall: ToolCall } }
| { type: 'TOOL_CALL_UPDATE'; payload: { id: string; updates: Partial<ToolCall> } }
| { type: 'TOOL_CALL_END'; payload: { id: string; result: string } }
| { type: 'TOOL_CALL_ERROR'; payload: { id: string; error: string } }
状態更新の補助関数
// 类型安全的状态更新辅助函数
function updateMessage(state: AppState, messageId: string, updates: Partial<Message>): AppState {
const messageIndex = state.conversation.messages.findIndex(msg => msg.id === messageId)
if (messageIndex === -1) {
console.warn(`Message with id ${messageId} not found`)
return state
}
const updatedMessages = [...state.conversation.messages]
updatedMessages[messageIndex] = {
...updatedMessages[messageIndex],
...updates
}
return {
...state,
conversation: {
...state.conversation,
messages: updatedMessages
}
}
}
function addMessage(state: AppState, message: Message): AppState {
return {
...state,
conversation: {
...state.conversation,
messages: [...state.conversation.messages, message]
}
}
}
🧪 状態のテスト
ユニットテスト
import { appStateReducer, initialAppState } from '@/lib/app-state-manager'
describe('App State Reducer', () => {
it('should add message correctly', () => {
const message: Message = {
id: 'test-1',
role: 'user',
content: 'Hello',
timestamp: Date.now()
}
const action = { type: 'MESSAGE_ADD', payload: { message } }
const newState = appStateReducer(initialAppState, action)
expect(newState.conversation.messages).toHaveLength(1)
expect(newState.conversation.messages[0]).toEqual(message)
})
it('should update connection state', () => {
const action = { type: 'CONNECTION_CONNECTING', payload: { url: 'ws://test' } }
const newState = appStateReducer(initialAppState, action)
expect(newState.connection.state).toBe('connecting')
expect(newState.connection.url).toBe('ws://test')
expect(newState.connection.error).toBeNull()
})
})
統合テスト
import { render, screen, fireEvent } from '@testing-library/react'
import { AppStateProvider } from '@/components/app-state-provider'
import { ChatInterface } from '@/components/chat-interface'
function TestWrapper({ children }: { children: React.ReactNode }) {
return (
<AppStateProvider>
{children}
</AppStateProvider>
)
}
describe('State Integration', () => {
it('should handle message sending flow', async () => {
render(
<TestWrapper>
<ChatInterface />
</TestWrapper>
)
const input = screen.getByPlaceholderText('输入你的问题...')
const sendButton = screen.getByRole('button', { name: /发送/i })
fireEvent.change(input, { target: { value: 'Test message' } })
fireEvent.click(sendButton)
expect(await screen.findByText('Test message')).toBeInTheDocument()
expect(input).toHaveValue('')
})
})
🔧 デバッグツール
Redux DevTools との統合
// 开发模式下启用Redux DevTools
function createAppStateProvider() {
const [state, dispatch] = useReducer(
appStateReducer,
initialAppState,
(initial) => {
// Redux DevTools支持
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
const devTools = (window as any).__REDUX_DEVTOOLS_EXTENSION__
if (devTools) {
devTools.connect({
name: 'ASR-LLM-TTS Frontend',
trace: true
})
}
}
return initial
}
)
// 开发模式下记录所有状态变更
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
console.log('State updated:', state)
}
}, [state])
return { state, dispatch }
}
状態スナップショットツール
// 状态快照和恢复工具
function useStateSnapshot() {
const { state, dispatch } = useAppState()
const createSnapshot = useCallback(() => {
const snapshot = {
timestamp: Date.now(),
state: JSON.parse(JSON.stringify(state))
}
localStorage.setItem('app-state-snapshot', JSON.stringify(snapshot))
return snapshot
}, [state])
const restoreSnapshot = useCallback(() => {
const saved = localStorage.getItem('app-state-snapshot')
if (saved) {
const snapshot = JSON.parse(saved)
dispatch({ type: 'STATE_RESTORE', payload: snapshot.state })
}
}, [dispatch])
return { createSnapshot, restoreSnapshot }
}
状態管理システムは、フロントエンドアプリケーションに安定的で予測可能かつ高パフォーマンスなデータフロー管理を提供し、複雑なインタラクションの場面でも状態の一貫性と良好なユーザー体験を確保します。
ドキュメント