如何使用 Node.js 创建漂亮的命令行交互

174 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

1_FBo_lJ6N6qevDiBlPK-0MA.png

Node.js 有很多竞争者。一段时间以来最知名的竞争对手是 Deno。但是,不要误以为 Node.js 已经过时。它一直存在很多问题和争议,但时至今日,它仍然是一个不错的选择。

Deno 仍有很多工作要做。它最初的炒作似乎已经降温。Node.js 的生态系统非常庞大,Deno 的成熟和使用还需要一点时间。

在本文中,我想通过创建命令行交互式用户界面来简单的举例说明 Node.js 生态系统的丰富性。通过使用Inquirer.js来创建漂亮的命令行交互。 您可能已经用过它,其中RemixNextJs正在使用Inquirer.js

安装

它是一个npm依赖项,因此只需执行以下操作即可轻松安装:

$ npm install --save inquirer

尽管commonjs模块多年来一直是 node 的首选,但v9版本和更高版本都是基于本地esm模块构建的。Node从 v12版本 开始就支持esm模块。

如果您需要它在较旧的Node版本上运行,则必须安装它的最新v8版本。

*// will install the latest 8 versions*\
npm install --save inquirer@^8.0.0

开始使用该库所需要做的就是简单地导入它。这是执行此操作的代码:

 // v8 and lower
const inquirer = require('inquirer'); 
// v9 an higher
import inquirer from 'inquirer';

Prompts

Prompts API 非常直观的围绕着Promises,这使得它非常易于使用和推理。或者,我们也可以使用他们的文档Rx.Observales中提到的React Interface 。在本文中,我们将重点介绍基于 Promise 的接口。

我们将某些内容打印到屏幕上的方式非常简单:

inquirer.prompt(questions) -> promise

对于复杂的用例,可以使用inquirer.registerPromptinquirer.createPrompModule.

Questions —— 问题

inquirer.prompt可以定义向用户提出的问题的方式。

由于交互的问题种类不同,inquirer为每个问题提供很多参数,举例如下几种:

  • validate: 接收用户输入并返回truefalse。也可以通过使用this.async()方法来实现异步。
  • filter:过滤用户输入的方法。
  • transform:一种转换用户输入的方法。
  • default: 值或函数返回问题的默认值。如果用户输入enter,它将默认为这个。
  • when:有条件地隐藏/显示问题的功能。

最重要key的是哪个类型。如果省略,它将默认为input. 让我们看看我们可以使用的不同类型:

1.input

这只是最基本的一个类型。代码如下:

import inquirer from 'inquirer';                                               

inquirer.prompt([
    {
        type: 'input',
        name: 'favColor', 
        message: 'What is your favorite color?',
        default: 'Blue'
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

结果如下:

截屏2022-10-08 下午4.03.41.png

2.List

要使用该list类型,我们需要设置type: 'list'并提供一个choices数组列表。我们可以disabled使用分隔符显示选项并设置布局样式new inquirer.Separator

import inquirer from 'inquirer';
inquirer.prompt([
    {
        type: 'list',
        name: 'contactSupport',
        message: 'How do you want to contact support?', 
        choices: [ 
            'By email',
            'By phone',
            { 
                name: 'By fax',
                disabled: 'Not supported anymore',
            },
            new inquirer.Separator(),
            'Have our agents call you',
        ],
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

结果如下:

截屏2022-10-08 下午4.12.34.png

3、rawlist

rawlist 不接受数组选项中有对象,只接受字符串。它让我们可以使用光标回复或直接输入选项编号。它将验证输入索引。

import inquirer from 'inquirer';
inquirer.prompt([
    {
        type: 'rawlist',
        name: 'contactSupport',
        message: 'How do you want to contact support?', 
        default: 'By phone',
        choices: [ 
            'By email',
            'By phone',
            'Have our agents call you',
        ],
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

结果如下:

截屏2022-10-08 下午4.15.44.png

4、expend

扩展类型与前一种类似,不同之处在于它将隐藏所有选项以减少冗长的界面。它将仅公开key每个选择的绑定。默认选项为大写。

建议将默认选项保留为空白,因此如果用户键入 enter,将给他们显示选项列表。

import inquirer from 'inquirer';
inquirer.prompt([
    {
        type: 'expend',
        name: 'support',
        message: 'How do you want to contact support?', 
        default: 'email',
        choices: [ 
            { 
                key: 'e',
                name: 'By email',
                value: 'email' 
            },
            {  
                key: 'p',
                name: 'By phone',
                value: 'phone'
            },
            { 
                key: 'a',
                name: 'Have our agents call',
                value: 'agent'
            },
        ],
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

5.Checkbox

复选框的特殊性在于,要具有默认值,我们必须将其标记为check: true选项数组。

import inquirer from 'inquirer';
inquirer.prompt([
    {
        type: 'checkbox',
        name: 'support',
        message: 'Select your contact support preferences?', 
        choices: [ 
            { 
                name: 'By email'
            },
            { 
                name: 'By chat',
                checked: false,
            },
            { 
                name: 'By pone',
                checked: true,
            },
            {
                name: 'Have our agents call',
            },
        ],
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

结果如下:

截屏2022-10-08 下午4.47.34.png

6.Confirm

这是一种在许多项目中使用的常见模式。

import inquirer from 'inquirer';
inquirer.prompt([
    {
        type: 'confirm',
        name: 'contactByEmail',
        message: 'Do you want to be contacted by email', 
        defalut: false
        
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

结果如下:

截屏2022-10-08 下午5.00.25.png

7.Editor

编辑器将启动默认终端编辑器以获得更舒适的输入。

import inquirer from 'inquirer';
inquirer.prompt([
    {
        type: 'editor',
        name: 'support',
        message: 'Please write your support query.', 
        waitUserInput: true
        
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

结果如下:

截屏2022-10-08 下午5.02.18.png

8.Password

还有一种输入敏感数据的类型。这将完全隐藏输入(如输入 sudo 密码时)或用我们喜欢的字符屏蔽输入。

import inquirer from 'inquirer';
const requireLetterAndNumber = (value) => {
    if (/\w/.test(value) && /\d/.test(value)) {
        return true;
    }
    return 'Password need to have at least a letter and a number';
};
inquirer.prompt([
    {
        type: 'Password',
        message: 'Enter a masked password',
        name: 'password2',
        mask: '*', 
        validate: requireLetterAndNumber,
        
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

结果如下:

截屏2022-10-08 下午5.06.23.png\

问题关联

在某些情况下,您可能希望根据以前的答案包含或删除问题。有两种方法可以解决:

  •  when:方法返回true时显示问题,false时隐藏问题。
  • 嵌套提示:将 promise 的结果与之前的答案嵌套在另一个提示中。

when

import inquirer from 'inquirer'; |                                                     inquirer.prompt([
    {
        type: 'confirm',
        name: 'pizzaFav',
        message: 'Is pizza your favorite food?'
    },
    {
        type: 'input',
        name: 'why',
        message: 'Oh! What could be more delicious than pizza?'
        when: (answers) => answers.pizzaFav === false 
    },
]).then(answers => {
    console.info('Your answer are :', JSON.stringify(answers));
});

嵌套提示

import inquirer from 'inquirer';
inquirer.prompt([
    {
        type: 'confirm',
        name: 'pizzaFav',
        message: 'Is pizza your favorite food?'
    }
]).then(answers => {
     if(!answers.pizzaFav) {
         inquirer.prompt({
             type: 'input',
             name: 'why',
             message: 'Oh! What could be more delicious than pizza?',
         });    
     }
});

上面的代码片段产生相同的结果:

截屏2022-10-08 下午5.16.52.png

总结:

Inquirer库使 Node.js 中任何基于设置过程的设置过程变得有趣和简单。它为我们想要显示的内容提供了很多控制和粒度。有很多自定义,代码干净易读。

创建一个引人入胜且简单的设置过程可能是成功的关键。Inquirer承担了我们所有的繁重工作,因此我们可以专注于真正重要的事情!