You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Redux中非可序列化数据的存储方案及最佳实践咨询

Handling Non-Serializable Connections (Bluetooth/WLAN, Media Streams) in 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:45:51