如果自定义 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 的处理,如果你删除一些代码可能会导致界面也不会正常显示。

相关推荐
朦胧之6 天前
React Native 新架构 (一)
前端·react native
野槐8 天前
react实例与总结(一)
javascript·react native·react.js
GISer_Jing9 天前
React Native:跨平台移动应用开发的明星框架
javascript·react native·react.js
No Silver Bullet11 天前
ReactNative进阶(五十九):存量 react-native 项目适配 HarmonyOS NEXT
react native·react.js·harmonyos
江湖行骗老中医11 天前
React Native 开发 安卓项目构建工具Gradle的配置和使用
android·react native·react.js
@大迁世界11 天前
React Native 列表组件:FlashList、FlatList 及更多
javascript·react native·react.js·ecmascript
screct_demo17 天前
详细介绍 React Native 的动画系统。主要包括 Animated 组件的各种用法:
javascript·react native·react.js
朦胧之18 天前
Expo 框架开发移动应用
前端·react native
@大迁世界19 天前
React Native 0.77 发布:更强的样式支持与性能优化
javascript·react native·react.js·ecmascript
Orange30151121 天前
react-native网络调试工具Reactotron保姆级教程
前端·react native