如何通过Tampermonkey脚本读取React应用的Redux Store状态?
嘿,这个问题问得好!React DevTools能在完全不改动目标页面代码的情况下读取状态,核心是它利用了React内部暴露的全局钩子和属性——咱们完全可以把这个思路搬到Tampermonkey脚本里来实现。下面给你几个可行的方案:
方案1:借助React DevTools的全局钩子__REACT_DEVTOOLS_GLOBAL_HOOK__
这是React DevTools的核心依赖,React加载完成后会自动在全局注册这个对象。我们可以通过它获取页面的React Fiber节点树,进而定位到目标组件的状态。
实现步骤:
- 先等待
__REACT_DEVTOOLS_GLOBAL_HOOK__初始化完成(确保React已经加载) - 通过钩子获取React渲染器实例,再找到根Fiber节点
- 遍历Fiber树,定位目标组件,读取其状态(类组件和函数组件的状态存储位置不同)
Tampermonkey脚本示例:
// ==UserScript== // @name 读取React应用状态 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 借助React DevTools钩子访问React内部状态 // @author You // @match https://你的React应用域名/* // @grant none // ==/UserScript== (function() { 'use strict'; // 等待React DevTools钩子加载完成 function waitForDevToolsHook() { return new Promise(resolve => { if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) { resolve(); } else { setTimeout(() => waitForDevToolsHook().then(resolve), 100); } }); } waitForDevToolsHook().then(() => { const devToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; // 获取第一个渲染器实例(通常对应页面主React应用) const renderer = devToolsHook.renderers.get(1); if (!renderer) return; // 获取根Fiber节点(这里以document.body为例,可根据实际根元素调整) const rootFiber = renderer.findFiberByHostInstance(document.body); // 递归遍历Fiber树,查找目标组件 function traverseFiberTree(fiber) { // 通过组件名称定位(如果是匿名组件,可改用props特征判断) if (fiber.type?.name === '你要找的组件名称') { // 类组件的状态存在stateNode.state中 if (fiber.stateNode?.state) { console.log('找到类组件状态:', fiber.stateNode.state); } // 函数组件的状态存在memoizedState中(React 16+) else if (fiber.memoizedState) { console.log('找到函数组件状态:', fiber.memoizedState); } return; } // 递归遍历子节点和兄弟节点 if (fiber.child) traverseFiberTree(fiber.child); if (fiber.sibling) traverseFiberTree(fiber.sibling); } traverseFiberTree(rootFiber); }); })();
注意事项:
- React版本兼容性:不同React版本的Fiber结构有差异(比如React 18和React 16的
memoizedState格式不同),可能需要根据目标应用的React版本调整代码 - 组件定位:如果目标组件是匿名函数组件,无法通过
type.name判断,可改用组件的props特征(比如特定的className、id属性)来定位 - 全局钩子稳定性:这属于React的非官方内部API,未来版本可能会有变动,需要做好兼容性测试
方案2:直接查找全局暴露的React根实例
部分React应用会把根实例挂载到全局对象上(比如window.__reactRoot、window.appRoot这类自定义全局变量),如果你的目标应用有这种情况,可以直接访问:
// 假设根实例挂载在window.__reactRoot const rootInstance = window.__reactRoot; // 类组件根实例直接取state console.log('根组件状态:', rootInstance.state); // 函数组件需要通过_internalRoot获取Fiber节点 const rootFiber = rootInstance._internalRoot.current; console.log('函数组件根状态:', rootFiber.memoizedState);
这种方法的局限性很大,只有当应用主动暴露根实例时才能用,但胜在简单直接。
方案3:通过DOM元素反向关联Fiber节点
如果你能找到目标组件对应的DOM元素,可以通过它反向找到对应的Fiber节点,再往上遍历找到父组件的状态:
// 先找到页面上的目标DOM元素 const targetDom = document.querySelector('#某个特定元素ID'); if (!targetDom) return; const devToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; const renderer = devToolsHook.renderers.get(1); // 通过DOM元素获取对应的Fiber节点 const fiber = renderer.findFiberByHostInstance(targetDom); // 往上遍历父Fiber节点,找到目标组件 let parentFiber = fiber.return; while (parentFiber) { if (parentFiber.type?.name === '目标父组件名称') { console.log('父组件状态:', parentFiber.stateNode.state); break; } parentFiber = parentFiber.return; }
这种方法适合你能通过DOM定位到组件的场景,比遍历整个Fiber树更高效。
总的来说,__REACT_DEVTOOLS_GLOBAL_HOOK__是最可靠的方案,毕竟这是React DevTools官方使用的机制,兼容性相对最好。不过因为是内部API,使用时要做好版本适配和异常处理哦。
内容的提问来源于stack exchange,提问作者mjuopperi
相关产品推荐
相关产品推荐

