如果自定义 react-native 的 devtools ?

前提:

  1. 成功执行了 npx react-native start
  2. 开启 Debug 。

此时可以在浏览器中打开 http://localhost:8081/launch-js-devtools 那么就会启动对应的 debugger ,当然如果你没有自定义,那么默认就会打开 http://localhost:8081/debugger-ui/ ,这是官方默认的 debugger 工具。

注意:目前官方在 0.73 已经去掉了这种方式的 debug ,推荐使用 flipper 。这个仅限学习实践。

我们可以看到当输入 http://localhost:8081/launch-js-devtools 的时候,会执行下面的代码:

ts 复制代码
function launchDevTools(
  {host, port, watchFolders}: LaunchDevToolsOptions,
  isDebuggerConnected: () => boolean,
) {
  // Explicit config always wins
  const customDebugger = process.env.REACT_DEBUGGER;
  if (customDebugger) {
    startCustomDebugger({watchFolders, customDebugger});
  } else if (!isDebuggerConnected()) {
    // Debugger is not yet open; we need to open a session
    launchDefaultDebugger(host, port);
  }
}

从代码中可以看出自定义命令是从 REACT_DEBUGGER 环境变量中取的,只要有这个环境变量,那么就会启动自定义的 devtool 。我们可以把默认的 devtool 工具拷贝出来,以备我们待会儿测试使用,下载地址为: github.com/react-nativ... 。然后利用 VScode 中的 LiveServer 来快速部署这个页面。

注意:cli-debugger-ui/src/ui 部署这个就可以,这是已经写好的页面,不需要重新构建,如果你觉得样式跟官方的不同,你需要在 head 标签中增加 即可。

如果你是 window 用户,那么修改你项目的 start 指令,改为: set REACT_DEBUGGER=start http://localhost:5500/ && react-native start ,其结果为:

此时再执行 yarn startnpm run start 。然后你再刷新一下 http://localhost:8081/launch-js-devtools 就可以看到启动了你的自定义 devtool

如果你是 macos 或者其他,那么只需要改成 REACT_DEBUGGER=start http://localhost:5500/ && react-native start 即可,其实就是删除前面的 set

2024/4/21 修正。

上面的方式并不能正常部署,因为自己开启的端口号是 5500 ,而我们开发服务器端口号为 8081 ,端口号不同就会在浏览器中出现跨域的问题,所以需要部署到不检查跨域的客户端上,比如 electron 里面。如果你尝试开启的端口号都是一个,那么在开启第二个的时候就会出现报错,说端口号被占用了。当然你可以选择关闭跨域开实现这个功能。可以使用这篇文章说的插件来解决 CORS 问题, blog.51cto.com/u_15292634/...

注意 github.com/react-nativ... 这里面的代码不能直接用,需要先编译;当然也可以不编译,毕竟这个代码很简单,我们只需要把那些不支持的改成支持的就可以,比如样式,我们删掉,放到 html 里面,还有图片改成 html 的方式,如果你会前端这个问题很好解决,如果这个不清楚可以留言。

下面我们分析官方默认的 devtool 。先看 index.js ,其中最关键的就是连接 websocket ,也就是跟开发服务器建立好连接:

ts 复制代码
const ws = new WebSocket(
  'ws://' +
    window.location.host +
    '/debugger-proxy?role=debugger&name=Chrome',
);

然后通过 onmessage 方法来获取开发服务器发来的信息:

ts 复制代码
ws.onmessage = async function (message) {
  if (!message.data) {
    return;
  }
  const object = JSON.parse(message.data);

  if (object.$event === 'client-disconnected') {
    shutdownJSRuntime();
    Page.setState({status: {type: 'disconnected'}});
    return;
  }

  if (!object.method) {
    return;
  }

  // Special message that asks for a new JS runtime
  if (object.method === 'prepareJSRuntime') {
    // 关闭之前创建的 JSRuntime
    shutdownJSRuntime();
    // 清空控制台中的日志
    console.clear();
    // 创建新的 JSRuntime
    createJSRuntime();
    // 向开发服务发送消息
    ws.send(JSON.stringify({replyID: object.id}));
    // 更新界面
    Page.setState({status: {type: 'connected', id: object.id}});
  } else if (object.method === '$disconnected') {
    // 关闭 JSRuntime
    shutdownJSRuntime();
    // 更新界面
    Page.setState({status: {type: 'disconnected'}});
  } else {
    // 将开发服务器传过来的信息发送给 worker
    worker.postMessage(object);
  }
};

关于 Worker ,可以参考 mdn 的教程。我们看到主要的工作就是处理当建立连接的时候,也就是 methodprepareJSRuntime 时创建好 Worker ,也就是 JSRuntime 。还有其他什么信息都是直接发给 Worker 。至于创建和关闭 Worker 的方法如下:

ts 复制代码
function createJSRuntime() {
  // This worker will run the application JavaScript code,
  // making sure that it's run in an environment without a global
  // document, to make it consistent with the JSC executor environment.
  worker = new Worker('./debuggerWorker.js');
  worker.onmessage = function (message) {
    ws.send(JSON.stringify(message.data));
  };
  window.onbeforeunload = function () {
    return (
      'If you reload this page, it is going to break the debugging session. ' +
      'Press ' +
      refreshShortcut +
      ' on the device to reload.'
    );
  };
  updateVisibility();
}

这是创建 Worker ,创建完成后,监听从 Worker 来的消息,不做任何处理,直接发送给开发服务器。

ts 复制代码
function shutdownJSRuntime() {
  if (worker) {
    worker.terminate();
    worker = null;
    window.onbeforeunload = null;
  }
}

这是关闭 Worker

接下来就是看 Worker 具体怎么处理数据,定位到 debuggerWorker.js 文件:

js 复制代码
returnValue = __fbBatchedBridge[object.method].apply(
  null,
  object.arguments,
);

这是关键代码,专门负责通信的函数,主要有下面的这些函数: callFunctionReturnFlushedQueueinvokeCallbackAndReturnFlushedQueue 。这些函数都是 MessageQueue.js 中定义的,这个函数主要处理桥通信的。关于 react-native 桥的通信可以参考:Go Deep into How React Native Interacts with The Bridge

注意你一旦开启了 Debug 调试模式,你的所有通信都会经过 debuggerWorker.js 这个 Worker 的处理,如果你删除一些代码可能会导致界面也不会正常显示。

相关推荐
番茄小酱0015 小时前
Expo|ReactNative 中实现扫描二维码功能
javascript·react native·react.js
少恭写代码1 天前
duxapp放弃了redux,在duxapp中局部、全局状态的实现方案
react native·taro·redux·duxapp
番茄小酱0012 天前
ReactNative中实现图片保存到手机相册
react native·react.js·智能手机
EBABEFAC3 天前
响应式编程-reactor
java·开发语言·react native
Engss7 天前
Taro React-Native Android apk 打包
android·react native·taro
镰刀出海8 天前
RN开发环境配置与Android版本app运行
android·react native
wills77710 天前
Flutter 状态管理框架Get
flutter·react native
MavenTalk10 天前
前端跨平台开发常见的解决方案
前端·flutter·react native·reactjs·weex·大前端
起司锅仔11 天前
ReactNative TurboModule(3)
android·javascript·react native·react.js
起司锅仔11 天前
ReactNative 简述(1)
android·javascript·react native·react.js