Redux中非可序列化数据的存储方案及最佳实践咨询
Great question—you’ve hit on a critical Redux best practice: never store non-serializable values like active connection instances or media streams directly in the Redux store. Redux’s state is designed to be plain, serializable data (objects, arrays, primitives) to enable time-travel debugging, state persistence, and consistent middleware behavior. Storing live connections here would break those guarantees and lead to hard-to-debug issues.
Here are the most common, battle-tested approaches to handle this scenario:
1. Store Metadata Only, Keep Live Connections in a Separate Service
The simplest and most widely recommended pattern is to split your concerns:
- Redux store: Holds only serializable metadata about your connections (e.g., device IDs, connection status, last received data snapshot, error messages).
- Separate service: A singleton class (like
ConnectionManager) that manages the live, non-serializable connection instances internally.
Example Implementation:
Connection Manager Singleton
class ConnectionManager { constructor() { this.activeConnections = new Map(); // Key: device ID, Value: live connection instance } async connect(device, request, dispatch) { // Create the live connection const connection = await instance.connect(device, request).listen(); // Store the connection internally this.activeConnections.set(device.id, connection); // Update Redux with connection metadata dispatch({ type: 'connections/connected', payload: { deviceId: device.id, status: 'connected' } }); // Listen for incoming data and sync to Redux connection.on('data', (data) => { dispatch({ type: 'connections/updateData', payload: { deviceId: device.id, data } }); }); return connection; } send(deviceId, data) { const connection = this.activeConnections.get(deviceId); if (connection) connection.send(data); } disconnect(deviceId, dispatch) { const connection = this.activeConnections.get(deviceId); if (connection) { connection.unsubscribe(); this.activeConnections.delete(deviceId); dispatch({ type: 'connections/disconnected', payload: { deviceId } }); } } } // Export a singleton instance export const connectionManager = new ConnectionManager();
Redux Slice (Simplified)
const initialState = { connections: [] // Each entry: { deviceId: string, status: 'connected'|'disconnected', lastData: any } }; const connectionsSlice = createSlice({ name: 'connections', initialState, reducers: { connected: (state, action) => { const existing = state.connections.find(c => c.deviceId === action.payload.deviceId); if (existing) { existing.status = 'connected'; } else { state.connections.push(action.payload); } }, updateData: (state, action) => { const connection = state.connections.find(c => c.deviceId === action.payload.deviceId); if (connection) connection.lastData = action.payload.data; }, disconnected: (state, action) => { state.connections = state.connections.filter(c => c.deviceId !== action.payload.deviceId); } } });
Usage in a Component
function DeviceControl({ device }) { const dispatch = useDispatch(); const connectionStatus = useSelector(state => state.connections.connections.find(c => c.deviceId === device.id)?.status ); const handleConnect = async () => { await connectionManager.connect(device, request, dispatch); }; const handleSendData = (data) => { connectionManager.send(device.id, data); }; return ( <div> <button onClick={handleConnect} disabled={connectionStatus === 'connected'}> Connect </button> {/* ... send data UI ... */} </div> ); }
2. Use Custom Redux Middleware to Manage Connections
If you prefer to keep connection logic tied to Redux’s action flow, you can build a custom middleware that holds the live connection instances. The middleware will listen for specific actions (like CONNECT_DEVICE, SEND_DATA) to manage connections, and dispatch new actions to update the store with metadata.
Example Middleware:
const connectionMiddleware = storeApi => next => action => { // Hold live connections inside the middleware const activeConnections = new Map(); switch (action.type) { case 'connections/connect': { const { device, request } = action.payload; instance.connect(device, request).listen() .then(connection => { activeConnections.set(device.id, connection); // Update store with connection status storeApi.dispatch({ type: 'connections/connected', payload: { deviceId: device.id } }); // Sync incoming data to store connection.on('data', data => { storeApi.dispatch({ type: 'connections/updateData', payload: { deviceId: device.id, data } }); }); }); break; } case 'connections/sendData': { const { deviceId, data } = action.payload; const connection = activeConnections.get(deviceId); if (connection) connection.send(data); break; } case 'connections/disconnect': { const { deviceId } = action.payload; const connection = activeConnections.get(deviceId); if (connection) { connection.unsubscribe(); activeConnections.delete(deviceId); storeApi.dispatch({ type: 'connections/disconnected', payload: { deviceId } }); } break; } default: return next(action); } };
This keeps all connection logic encapsulated in middleware, and your components only need to dispatch actions to interact with connections.
3. Combine with React Context (For Component-Level Access)
If your connection instances are primarily used within React components, you can wrap the ConnectionManager singleton in a React Context. This lets components access the manager directly without importing it everywhere, while still using Redux to track connection state.
Example Context:
const ConnectionManagerContext = React.createContext(); export function ConnectionManagerProvider({ children }) { return ( <ConnectionManagerContext.Provider value={connectionManager}> {children} </ConnectionManagerContext.Provider> ); } // Hook for components to use export function useConnectionManager() { return React.useContext(ConnectionManagerContext); }
Then in components, you’d use useConnectionManager() to get the instance, and useSelector to get connection state from Redux.
Key Takeaways
- Redux state = serializable metadata only: Never store live instances, functions, or non-serializable objects here.
- Live connections belong in external services/middleware: These layers can manage side effects and live state without breaking Redux’s rules.
- Sync metadata to Redux: Use actions to keep the store updated with connection status, data snapshots, etc., so components can react to changes.
内容的提问来源于stack exchange,提问作者scrimau

