Ink进度指示器:优雅的任务进度显示实现

Ink进度指示器:优雅的任务进度显示实现

【免费下载链接】ink 🌈 React for interactive command-line apps 【免费下载链接】ink 项目地址: https://gitcode.com/GitHub_Trending/in/ink

在命令行界面(CLI)应用开发中,实时显示任务进度是提升用户体验的关键功能。Ink作为React的命令行渲染器,提供了强大的组件化能力来构建优雅的进度指示器。本文将深入探讨如何在Ink中实现各种类型的进度显示,从简单的文本进度到复杂的可视化进度条。

进度指示器的核心价值

在CLI应用中,进度指示器不仅提供视觉反馈,还能:

  • 降低用户焦虑:长时间运行的任务需要明确的进度反馈
  • 提升专业性:精美的进度显示体现开发者的用心程度
  • 增强可访问性:为屏幕阅读器用户提供准确的进度信息

基础文本进度指示器

最简单的进度指示器使用文本显示百分比:

import React, {useState, useEffect} from 'react';
import {render, Text, Box} from 'ink';

function TextProgress({total = 100}) {
  const [progress, setProgress] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setProgress(prev => {
        if (prev >= total) {
          clearInterval(interval);
          return total;
        }
        return prev + 1;
      });
    }, 50);

    return () => clearInterval(interval);
  }, [total]);

  const percentage = Math.round((progress / total) * 100);

  return (
    <Box flexDirection="column">
      <Text color="cyan">处理中: {percentage}%</Text>
      <Text dimColor>{progress}/{total} 项已完成</Text>
    </Box>
  );
}

render(<TextProgress total={50} />);

可视化进度条实现

基础进度条组件

function ProgressBar({progress, total, width = 20}) {
  const percentage = progress / total;
  const filledWidth = Math.floor(width * percentage);
  const emptyWidth = width - filledWidth;
  
  const filledBar = '█'.repeat(filledWidth);
  const emptyBar = '░'.repeat(emptyWidth);
  
  return (
    <Box>
      <Text color="green">{filledBar}</Text>
      <Text dimColor>{emptyBar}</Text>
      <Text> {Math.round(percentage * 100)}%</Text>
    </Box>
  );
}

增强型进度条

function EnhancedProgressBar({
  progress,
  total,
  width = 30,
  showPercentage = true,
  showCount = true
}) {
  const percentage = progress / total;
  const filledWidth = Math.max(0, Math.min(width, Math.floor(width * percentage)));
  
  return (
    <Box flexDirection="column">
      <Box>
        <Text color="green" backgroundColor="green">
          {' '.repeat(filledWidth)}
        </Text>
        <Text dimColor backgroundColor="gray">
          {' '.repeat(width - filledWidth)}
        </Text>
        {showPercentage && (
          <Text> {Math.round(percentage * 100)}%</Text>
        )}
      </Box>
      {showCount && (
        <Text dimColor>
          {progress}/{total}
        </Text>
      )}
    </Box>
  );
}

多任务进度管理系统

对于复杂的多任务场景,需要更高级的进度管理:

function MultiTaskProgress() {
  const [tasks, setTasks] = useState([
    { id: 1, name: '下载文件', progress: 0, total: 100 },
    { id: 2, name: '处理数据', progress: 0, total: 200 },
    { id: 3, name: '生成报告', progress: 0, total: 150 }
  ]);

  useEffect(() => {
    const intervals = tasks.map((task, index) => {
      return setInterval(() => {
        setTasks(prev => prev.map(t => 
          t.id === task.id && t.progress < t.total
            ? {...t, progress: t.progress + 1}
            : t
        ));
      }, 100 + index * 50);
    });

    return () => intervals.forEach(clearInterval);
  }, []);

  const totalProgress = tasks.reduce((sum, t) => sum + t.progress, 0);
  const totalTotal = tasks.reduce((sum, t) => sum + t.total, 0);
  const overallPercentage = Math.round((totalProgress / totalTotal) * 100);

  return (
    <Box flexDirection="column" padding={1}>
      <Text bold underline>任务进度总览: {overallPercentage}%</Text>
      
      {tasks.map(task => {
        const percentage = Math.round((task.progress / task.total) * 100);
        return (
          <Box key={task.id} marginTop={1}>
            <Box width={20}>
              <Text>{task.name}</Text>
            </Box>
            <Box width={15}>
              <ProgressBar 
                progress={task.progress} 
                total={task.total} 
                width={10}
              />
            </Box>
            <Text> {percentage}%</Text>
          </Box>
        );
      })}
    </Box>
  );
}

spinner动画指示器

对于不确定进度的任务,spinner是理想选择:

function Spinner({isLoading = true}) {
  const [frame, setFrame] = useState(0);
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];

  useEffect(() => {
    if (!isLoading) return;
    
    const interval = setInterval(() => {
      setFrame(prev => (prev + 1) % frames.length);
    }, 80);

    return () => clearInterval(interval);
  }, [isLoading]);

  if (!isLoading) return null;

  return (
    <Text color="yellow">
      {frames[frame]} 处理中...
    </Text>
  );
}

高级特性:可访问性支持

Ink内置了强大的可访问性支持,确保进度指示器对屏幕阅读器友好:

function AccessibleProgress({progress, total}) {
  const percentage = Math.round((progress / total) * 100);
  
  return (
    <Box aria-role="progressbar" aria-state={{busy: progress < total}}>
      <Text aria-label={`进度: ${percentage}%`}>
        <ProgressBar progress={progress} total={total} />
      </Text>
      {progress < total && (
        <Text aria-hidden="true" dimColor>
          {progress}/{total}
        </Text>
      )}
    </Box>
  );
}

性能优化技巧

使用useMemo避免不必要的重渲染

function OptimizedProgressBar({progress, total}) {
  const percentage = useMemo(() => 
    Math.round((progress / total) * 100), 
    [progress, total]
  );

  const barVisual = useMemo(() => {
    const width = 20;
    const filled = Math.floor(width * progress / total);
    return '█'.repeat(filled) + '░'.repeat(width - filled);
  }, [progress, total]);

  return (
    <Box>
      <Text color="green">{barVisual}</Text>
      <Text> {percentage}%</Text>
    </Box>
  );
}

防抖更新避免闪烁

function DebouncedProgress({progress, total}) {
  const [displayProgress, setDisplayProgress] = useState(0);
  
  useEffect(() => {
    const timeout = setTimeout(() => {
      setDisplayProgress(progress);
    }, 100); // 100ms防抖
    
    return () => clearTimeout(timeout);
  }, [progress]);

  return <ProgressBar progress={displayProgress} total={total} />;
}

完整的进度指示器系统

class ProgressTracker {
  constructor() {
    this.tasks = new Map();
    this.listeners = new Set();
  }

  addTask(id, name, total) {
    this.tasks.set(id, {id, name, progress: 0, total});
    this.notifyListeners();
  }

  updateProgress(id, progress) {
    const task = this.tasks.get(id);
    if (task) {
      task.progress = progress;
      this.notifyListeners();
    }
  }

  addListener(listener) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  notifyListeners() {
    this.listeners.forEach(listener => listener([...this.tasks.values()]));
  }
}

// React组件中使用
function ProgressSystem() {
  const [tasks, setTasks] = useState([]);
  const tracker = useMemo(() => new ProgressTracker(), []);

  useEffect(() => {
    return tracker.addListener(setTasks);
  }, [tracker]);

  const totalProgress = tasks.reduce((sum, t) => sum + t.progress, 0);
  const totalTotal = tasks.reduce((sum, t) => sum + t.total, 0);

  return (
    <Box flexDirection="column">
      <Text bold>总进度: {Math.round((totalProgress / totalTotal) * 100)}%</Text>
      {tasks.map(task => (
        <TaskProgress key={task.id} task={task} />
      ))}
    </Box>
  );
}

最佳实践总结

  1. 选择合适的指示器类型

    • 确定进度:使用进度条
    • 不确定进度:使用spinner
    • 多任务:使用分层进度显示
  2. 性能考虑

    • 使用防抖避免频繁更新
    • 合理设置更新频率(通常100-500ms)
    • 使用useMemo优化计算
  3. 可访问性

    • 为屏幕阅读器提供aria标签
    • 使用适当的ARIA角色和状态
    • 提供文本替代方案
  4. 用户体验

    • 显示有意义的进度信息
    • 提供预估时间(如果可能)
    • 在完成时给出明确反馈

通过Ink的强大组件系统,你可以创建出既美观又功能完善的命令行进度指示器,显著提升CLI应用的用户体验和专业感。

【免费下载链接】ink 🌈 React for interactive command-line apps 【免费下载链接】ink 项目地址: https://gitcode.com/GitHub_Trending/in/ink

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值