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

相关推荐
gxhlh2 天前
React Native防止重复点击
javascript·react native·react.js
miao_zz2 天前
基于react native的锚点
android·react native·react.js
卿卿qing4 天前
【React Native】路由和导航
javascript·react native·react.js
日渐消瘦 - 来自一个高龄程序员的心声4 天前
react native(expo)多语言适配
javascript·react native·react.js·expo
卿卿qing4 天前
【App】React Native
javascript·react native·react.js
日渐消瘦 - 来自一个高龄程序员的心声5 天前
react native(expo)选择图片/视频并上传阿里云oss
react native·react.js·阿里云
恋猫de小郭6 天前
鸿蒙版 React Native 正式开源,ohos_react_native 了解一下
react native·react.js·harmonyos
恋猫de小郭6 天前
React Native 0.76,New Architecture 将成为默认模式,全新的 RN 来了
javascript·react native·react.js
Atypiape213 天前
(零) React Native 项目开发拾遗
android·flutter·react native·ios·typescript·rn
小童不学前端14 天前
如何在移动端app里嵌套web页面之react-native-webview
react native·expo