在 React Native 0.74 项目中如何启用“websockets: false”回退到长轮询?

解读

国内 React Native 0.74 项目普遍使用 pouchdb-browserpouchdb-react-native 作为 CouchDB 同步端。
由于 Metro 打包器对 WebSocket 原生模块 的裁剪,以及国内部分机型(华为、荣耀)对 WebSocket 的省电策略,首次连接失败率可达 15%
面试官真正想考察的是:

  1. 你是否知道 pouchdb-adapter-http 的底层传输链;
  2. 能否在 0.74 新架构(TurboModules + Fabric) 下,把 WebSocket 关闭并强制降级到 HTTP 长轮询,同时保证 同步性能不崩溃
  3. 是否熟悉国内网络环境下的 反向代理(nginx + ws→wss)CDN 缓存头 冲突问题。

知识点

  • pouchdb-core 在创建 PouchDB 实例时,通过 ajax 字段注入自定义请求库;
  • pouchdb-adapter-http 优先检测全局 WebSocket,若显式传入 opts.websockets = false,则直接跳过 ws 协议,改用 ajax({ timeout, heartbeat }) 实现长轮询;
  • React Native 0.74 的 hermes 引擎已移除 window.WebSocket 全局变量,但 react-native-community/netinfo 仍会在某些时机提前加载 WebSocket polyfill,导致 opts.websockets = false 被覆盖
  • 国内 OSS 镜像(淘宝、华为)对 ** pouchdb 7.3.1** 的 dist 文件做了 gzip 双重压缩,若直接 import PouchDB from 'pouchdb',会触发 DEV 下的 source-map 告警,间接拖慢长轮询心跳。

答案

  1. 锁定依赖版本,禁止自动升级 WebSocket 相关包
    package.json 中追加
    "resolutions": { "ws": "~7.5.9", "react-native-polyfill-globals": "~3.0.1" }

  2. 在入口文件 index.js 最顶部,先于任何 import 抹掉 WebSocket

    global.WebSocket = undefined;          // 彻底移除 WebSocket 全局
    delete global.__PouchDBWebSocket;      // 防止 pouchdb 运行时再次挂载
    
  3. 创建自定义 ajax 包装,显式关闭 websockets 并开启长轮询心跳

    import PouchDB from 'pouchdb-core';
    import HttpAdapter from 'pouchdb-adapter-http';
    PouchDB.plugin(HttpAdapter);
    
    const ajax = ({ url, ...rest }) => {
      return fetch(url, {
        ...rest,
        timeout: 30000,          // 国内 4G 到 5G 切换时容忍 30s
        headers: {
          ...rest.headers,
          'X-WS-Fallback': '1',  // 给 nginx 日志打标,方便灰度
        },
      });
    };
    
    const localDB = new PouchDB('rn_cache');
    const remoteDB = new PouchDB('https://your-couch-cn-east-1.myqcloud.com/db', {
      adapter: 'http',
      ajax,
      websockets: false,         // 关键开关
      skip_setup: true,
      heartbeat: 10000,          // 10s 心跳,符合腾讯云 CLB 空闲超时 60s
    });
    
    localDB.sync(remoteDB, {
      live: true,
      retry: true,
      back_off_function: delay => Math.min(delay * 2, 60000), // 指数退避,封顶 1min
    });
    
  4. 验证回退是否生效
    Chrome DevTools → Network 中过滤 _changes?feed=longpoll,应看到 持续 25s 左右 的 pending 请求;若出现 101 Switching Protocols 则回退失败,需检查 步骤2 是否被热重载覆盖。

拓展思考

  • 如果面试官追问“长轮询仍出现 502”,可答:
    国内云厂商 CLB 默认 60s 断开,需在 nginx 增加
    proxy_read_timeout 3600s; proxy_send_timeout 3600s;
    并在 location /db/_changes 中单独 关闭缓存 proxy_buffering off;

  • 若业务需要 双向实时,但 WebSocket 被运营商 QOS 降速,可引入 pouchdb-replication-stream增量快照 + 断点续传,在 React Native 0.74 的 JSI 线程 中通过 C++ TurboModule 直接写文件,绕过 JavaScript 层长轮询性能瓶颈。