You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 17:54:49