React客户端JS编译器开发:异步代码执行与控制台输出捕获方案问询
客户端JS代码执行与控制台输出捕获方案
核心思路
通过重写浏览器原生console方法捕获所有输出(包括异步代码中的调用),使用React状态管理输出内容,同时保留原始对象结构用于展开/折叠渲染。
实现步骤
1. 控制台拦截与状态管理
在父组件中,通过useEffect临时替换console.log、console.error等方法,将输出内容存入React状态。同时监听window.onerror捕获异步代码中的未处理错误。
2. 执行用户代码
使用eval执行用户输入的代码,同步错误通过try-catch捕获,异步错误由全局错误监听处理。
3. 可展开对象渲染
编写递归组件ObjectViewer,直接渲染原始对象/数组,支持展开折叠查看完整结构。
完整代码示例
父组件(JSCompiler.js)
import { useState, useEffect } from 'react'; import OutputViewer from './OutputViewer'; function JSCompiler() { const [userCode, setUserCode] = useState(''); const [outputs, setOutputs] = useState([]); useEffect(() => { // 保存原生console方法 const originalLog = console.log; const originalError = console.error; const originalWarn = console.warn; // 重写console.log,捕获输出 console.log = (...args) => { setOutputs(prev => [...prev, { type: 'log', content: args }]); originalLog(...args); // 可选:保留浏览器原生控制台输出 }; // 重写console.error console.error = (...args) => { setOutputs(prev => [...prev, { type: 'error', content: args }]); originalError(...args); }; // 重写console.warn console.warn = (...args) => { setOutputs(prev => [...prev, { type: 'warn', content: args }]); originalWarn(...args); }; // 捕获异步未处理错误 const handleGlobalError = (message, source, lineno, colno, error) => { setOutputs(prev => [...prev, { type: 'error', content: [error || message] }]); return true; // 阻止默认错误提示 }; window.addEventListener('error', handleGlobalError); // 组件卸载时恢复原生console return () => { console.log = originalLog; console.error = originalError; console.warn = originalWarn; window.removeEventListener('error', handleGlobalError); }; }, []); const runCode = () => { setOutputs([]); // 清空历史输出 try { eval(userCode); } catch (error) { // 捕获同步执行错误 setOutputs(prev => [...prev, { type: 'error', content: [error] }]); } }; return ( <div style={{ display: 'flex', gap: '20px', padding: '20px' }}> <div style={{ flex: 1 }}> <textarea value={userCode} onChange={(e) => setUserCode(e.target.value)} rows={10} style={{ width: '100%', padding: '10px', fontSize: '14px' }} placeholder="输入JS代码..." /> <button onClick={runCode} style={{ marginTop: '10px', padding: '8px 16px' }}> 执行代码 </button> </div> <div style={{ flex: 1, border: '1px solid #eee', padding: '10px', borderRadius: '4px' }}> <h3>输出结果</h3> <OutputViewer outputs={outputs} /> </div> </div> ); } export default JSCompiler;
输出渲染组件(OutputViewer.js)
import { useState } from 'react'; // 递归渲染可展开的对象/数组 function ObjectViewer({ data }) { const [isExpanded, setIsExpanded] = useState(false); // 非对象类型直接显示 if (typeof data !== 'object' || data === null) { return <span>{String(data)}</span>; } const isArray = Array.isArray(data); const keys = isArray ? Array.from({ length: data.length }, (_, i) => i) : Object.keys(data); return ( <div style={{ marginLeft: '8px' }}> <span onClick={() => setIsExpanded(!isExpanded)} style={{ cursor: 'pointer', color: '#2563eb' }} > {isArray ? '[' : '{'} <span style={{ marginLeft: '4px' }}>{isExpanded ? '▼' : '▶'}</span> </span> {isExpanded && ( <div> {keys.map((key) => ( <div key={key} style={{ marginLeft: '16px' }}> <span style={{ color: '#6b7280' }}>{isArray ? '' : `${key}: `}</span> <ObjectViewer data={data[key]} /> </div> ))} <span>{isArray ? ']' : '}'}</span> </div> )} </div> ); } // 输出列表渲染 function OutputViewer({ outputs }) { return ( <div style={{ fontSize: '14px', marginTop: '10px' }}> {outputs.map((output, index) => ( <div key={index} style={{ marginBottom: '8px', color: output.type === 'error' ? '#dc2626' : output.type === 'warn' ? '#ca8a04' : '#16a34a' }} > {output.content.map((item, idx) => ( <span key={idx} style={{ marginRight: '8px' }}> <ObjectViewer data={item} /> </span> ))} </div> ))} </div> ); } export default OutputViewer;
关键特性说明
- 异步支持:因为
console拦截器在组件生命周期内持续生效,异步代码(如setTimeout)中的console调用会被自动捕获,状态更新后输出组件会实时渲染新内容。 - 完整对象结构:直接存储原始对象而非字符串,通过递归组件实现展开/折叠,保留对象的所有属性和层级关系。
- 错误捕获:覆盖同步执行错误(
try-catch)和异步未处理错误(window.onerror),确保所有错误都能在输出窗口展示。
注意事项
- 安全风险:
eval执行用户输入的代码存在XSS风险,仅适合执行可信代码,生产环境需添加严格的输入校验或沙箱隔离。 - 性能优化:若输出大量复杂对象,可添加渲染深度限制或虚拟滚动提升性能。
- 扩展支持:如需支持
console.table、console.dir等方法,可在拦截器中添加对应处理逻辑。
内容的提问来源于stack exchange,提问作者Thiru
相关产品推荐
相关产品推荐

