前提:
- 成功执行了
npx react-native start
; - 开启 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 start
或 npm 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
的教程。我们看到主要的工作就是处理当建立连接的时候,也就是 method
为 prepareJSRuntime
时创建好 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,
);
这是关键代码,专门负责通信的函数,主要有下面的这些函数: callFunctionReturnFlushedQueue
,invokeCallbackAndReturnFlushedQueue
。这些函数都是 MessageQueue.js 中定义的,这个函数主要处理桥通信的。关于 react-native
桥的通信可以参考:Go Deep into How React Native Interacts with The Bridge 。
注意你一旦开启了 Debug 调试模式,你的所有通信都会经过 debuggerWorker.js 这个 Worker 的处理,如果你删除一些代码可能会导致界面也不会正常显示。