React + Ink CLI教程--如何构建一个浏览器命令行应用程序

693 阅读9分钟

React是一个流行的前端JavaScript开发库。根据State Of JS 2021调查,它在认知度和使用率方面排名第一。

这意味着大多数的JavaScript开发人员可能知道或使用React。

尽管React在构建网络应用程序用户界面(UI)方面很受欢迎,但你也可以将React核心库用于其他方面。事实上,这个 [react-dom](https://reactjs.org/docs/react-dom.html)库是在网页上渲染用户界面的东西,而不是React本身。React更像是一个引擎,可以被移植到任何环境。

开发者喜欢React的一个原因是它构建用户界面的方法。你只需要描述界面应该是什么样子的,React引擎就会负责放置和页面上的任何变化。

有一些库使用React来帮助开发者创建除网络应用之外的其他类型的应用程序,它们包括:

在本教程中,我们将探讨命令行界面。我们还将建立一个应用程序,显示一些选定的加密货币和代币的实时价格。为了帮助获得代币的价格,我们将使用CoinGecko的API

项目的工作版本可以在GitHub上找到。

注意:本文假设你能使用React来构建基本的前端网络应用。如果没有,这里有一个关于React JS的免费CodeCamp课程。本文还假设你能使用REST APIs,并且知道命令行上的基本命令,因为这些在本文中没有涉及。

好了,让我们开始吧。

什么是命令行界面? (CLI)

命令行界面是一种你可以通过文本与计算机互动的方式。它通过你在命令提示符中输入特殊命令来工作:

command

Windows操作系统中的命令行界面

图形用户界面(GUI)产生之前,这是开发人员与计算机互动的方式。命令行界面在自动化任务和一般的软件开发中仍然很有用。

什么是Ink?

Ink是一个将React带到命令行的JavaScript库。它有助于使用基于组件的UI元素的概念开发CLI应用程序。

Ink允许你使用React的所有功能,包括基于类的组件、生命周期方法、功能组件、钩子,等等,用于构建命令行工具。

ink 库也有称为有用组件的插件。这些有用的组件并没有内置到ink 库中,而是由其他开发者构建的自定义组件,你可以将其导入到Ink项目中。

如何安装Ink

有两种安装Ink的方法它们是

在这篇文章中,我们将使用create-ink-app 方法来快速启动一个Ink项目。

在命令行中,导航到你想放置Ink项目的文件夹或目录,然后运行下面的命令:

npx create-ink-app crypto-cli

install-1

安装Ink

这个命令在我们运行命令的文件夹内安装建立Ink项目所需的文件。在我们的例子中,文件夹和项目的名称是一样的(crypto-cli)。

create-ink-app 还为我们的项目生成了一个可执行的命令,这样我们就可以通过在CLI上调用它的名字来运行我们的应用程序。

就这样,Ink 3(截至本文时,这是Ink的最新版本)已经安装完毕,我们准备开始构建命令行应用程序。

当我们运行crypto-cli 命令时,我们得到这样的输出。

hello

运行的输出crypto-cli

为什么我们会有这样的输出?让我们探讨一下create-ink-app 安装的文件。

检查Ink安装的文件

项目的文件结构看起来像这样:

files

文件和文件夹create-ink-app提供

这些文件和文件夹是做什么的?

  • node_modules:这个文件夹存放着我们的应用程序正常运行所需的所有软件包。这些包包括reactink ,但也包括reactink 的依赖项(如果有的话)。node-modules 也包括ink 的创建者认为会提供良好的开发者体验的包。
  • .editor-config: 这个文件有助于保持代码的一致性。很多开发者可能在这个项目上使用不同的IDE工作。为了确保编码风格一致,你可以使用.editor-config 。你可以在这里找到更多关于它的信息。
  • .gitattributes编写文件:我们将用它来配置我们的文件的属性,这些属性将被版本控制程序Git使用。你可以在这里找到更多信息。对于这个项目,我们不需要在这个文件中添加或删除任何东西。
  • cli.js: 在这个文件中,我们将使用ink 来渲染我们的应用程序。
  • package-lock.json: 我们用它来锁定我们应用程序的依赖关系到一个特定的版本,这样别人就可以随时随地轻松地复制我们的项目。
  • package.json文件:包含我们应用程序的元数据,包括名称、版本和依赖性。
  • readme.md: 为我们的项目提供一个标记性的readme文件。
  • test.js: 用于在我们的应用程序中编写测试。我们将不会在我们的项目中编辑这个文件。
  • ui.jsReact文件:这是用React进行前端Web开发的同义词App.js 。它导入并包含了我们项目将拥有的每一个组件。

看看package.json ,就会发现我们所安装的依赖性:

...,
"dependencies": {
    "import-jsx": "^4.0.1",
    "ink": "^3.2.0",
    "meow": "^9.0.0",
    "react": "^17.0.2"
},
...

我们项目的依赖性

你可能不熟悉import-jsxmeow 。让我们来看看它们的作用。

  • import-jsx:你使用这个库来导入和转译JSX文件,在ink
  • meow:CLI命令接受参数。meow 帮助我们在ink 中实现。

说得够多了,让我们开始构建。

如何构建CLI应用程序

在本教程中,正如我前面提到的,我们将建立一个应用程序,使用CoinGecko API显示一些加密货币和代币的价格。

如何创建页眉

我们将导入一个名为ink-big-text 的npm包。它是Ink提供的 "有用组件 "之一。我们将使用它在命令行中创建一个大头。

我们还将安装ink-gradient 来美化我们的页眉。它是Ink提供的另一个 "有用的组件"。

npm install ink-big-text ink-gradient

然后我们将编辑我们的ui.js ,它同样必须包含我们所有的组件:

// ui.js

const React = require('react');
const Gradient = require('ink-gradient');
const BigText = require('ink-big-text');

const App = () => (
	<Gradient name="summer">
		<BigText text="crypto cli" align='center' font='chrome'/>
	</Gradient>
);

module.exports = App;

而当我们运行crypto-cli ,代码就会转化为这个美妙的页眉:

header

页眉的输出

如何显示我们的数据

为了显示我们的数据,我们需要创建一个Box 元素,将其排列成表格形式。Box 的工作方式就像网页上的display: flex; 的容器。你可以像Flexbox元素一样对它进行样式设计。

在从CoinGecko提取数据之前,我们暂时先创建模拟数据。src 内的一个文件data.json 将容纳我们的模拟数据。你可以在这里找到模拟数据。

接下来,我们将在src 文件夹内创建一个名为components 的文件夹。 我们还将在components 文件夹中创建一个名为Table.js 的文件。

然后,以下代码将进入Table.js :

// Table.js

const React = require('react');

const { useState, useEffect } = React;
// Destructuring useState and useEffect from React

const { Box, Text, Newline } = require('ink');
// Destructuring the components we need from ink

const cryptoData = require('../data.json');
// Fetching mock data

const Table = () => {

    const [data, setData] = useState([]);

    useEffect(()=>{
        setData(cryptoData);
    });

    return (
        <Box borderStyle='single' padding={2} flexDirection='column'>
            <Box>
                <Box width='25%'><Text>COIN</Text></Box>
                <Box width='25%'><Text>PRICE (USD)</Text></Box>
                <Box width='25%'><Text>24 HOUR CHANGE</Text></Box>
                <Box width='25%'><Text>ALL TIME HIGH</Text></Box>
            </Box>
            <Newline/>
            {
                data.map(({id, name, current_price, price_change_percentage_24h, ath}) => (
                    <Box key={id}>
                        <Box width='25%'><Text>{name}</Text></Box>
                        <Box width='25%'><Text>{current_price}</Text></Box>
                        <Box width='25%'><Text>{price_change_percentage_24h}</Text></Box>
                        <Box width='25%'><Text>{ath}</Text></Box>
                    </Box>
                ))
            }
        </Box>
    )
}

module.exports = Table;

现在,我们将继续前进并将表组件导入我们的应用程序:

// ui.js

const React = require('react');
const Gradient = require('ink-gradient');
const BigText = require('ink-big-text');
const importJsx = require('import-jsx');
const Table = importJsx('./components/Table')

const App = () => (
	<>
		<Gradient name="summer">
			<BigText 
				text="crypto cli" 
				align='center' 
				font='chrome'
			/>
		</Gradient>
		<Table/>
	</>
);

module.exports = App;
(perhaps, remove the 'use strict')

运行crypto-cli ,我们会得到这个结果:

output

使用模拟数据的输出

我喜欢在我的CLI应用程序中进行一些装饰。所以我们将继续使用ink 为我们提供的颜色:

// Table.js

const React = require('react');

const { useState, useEffect } = React;

const { Box, Text, Newline } = require('ink');

const cryptoData = require('../data.json');

const Table = () => {

    const [data, setData] = useState([]);

    useEffect(()=>{
        setData(cryptoData);
    })

    return (
        <Box borderStyle='single' padding={2} flexDirection='column'>
            <Box>
                <Box width='25%'><Text>COIN</Text></Box>
                <Box width='25%'><Text>CURRENT PRICE (USD)</Text></Box>
                <Box width='25%'><Text>24 HOUR CHANGE</Text></Box>
                <Box width='25%'><Text>ALL TIME HIGH</Text></Box>
            </Box>
            <Newline/>
            {
                data.map(({id, name, current_price, price_change_percentage_24h, ath}) => (
                    <Box key={id}>
                        <Box width='25%'>
                            <Text>{name}</Text>
                        </Box>
                        <Box width='25%'>
                            <Text color='cyan'>{'$' + current_price.toLocaleString()}</Text>
                        </Box>
                        <Box width='25%'>
                            <Text backgroundColor={Math.sign(price_change_percentage_24h) < 0 ? 'red' : 'green'}>
                                {price_change_percentage_24h.toFixed(2) + '%'}
                            </Text>
                        </Box>
                        <Box width='25%'>
                            <Text color='green'>{'$' + ath.toLocaleString()}</Text>
                        </Box>
                    </Box>
                ))
            }
        </Box>
    )
}

module.exports = Table;

要清楚的是,为了给ink 中的文本组件添加颜色,我们使用了道具(属性)color 。为了添加背景色,我们使用了属性backgroundColor 。然后我们添加了逻辑,检查24小时的变化是负的还是正的。

如果变化是正的,我们确保背景色是绿色,否则背景色将是红色。

当我们运行crypto-cli ,我们有以下输出:

colored

添加样式后的输出

而手动否定data.json 中第二个24 HOUR CHANGE 的值,会产生以下输出:

negate

否定一个值后的输出

如何从CoinGecko API中获取数据

这个阶段是我们从CoinGecko API获取实际数据的地方。以下是我们需要采取的步骤:

coingecko

CoinGecko API页面

  • 导航到 "硬币 "部分并点击/coins/markets

coins

导航到/coins/markets

  • 点击 "试一试 "按钮。
  • 输入 "USD "作为vs_currency 。同时输入你喜欢的加密货币和代币的id (我使用了比特币、莱特币、Matic-network、以太坊、Tether、Binancecoin、Solana、Aave、Cardano和TRON)。记住,在输入币种ID的时候不要加空格。

vs

输入表格值

  • 点击执行按钮
  • 复制出它生成的链接。对我来说,这是我将用来进行API调用的链接。该链接取决于你选择的加密货币或代币。

gecko-link

复制突出显示的链接

https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin%2Clitecoin%2Cmatic-network%2Cethereum%2Ctether%2Cbinancecoin%2Csolana%2Caave%2Ccardano%2Ctron&order=market_cap_desc&per_page=100&page=1&sparkline=false

我们现在将移动到我们的Table.js ,进行API调用。

安装 [axios](https://github.com/axios/axios)这是一个对获取API数据有用的npm库。

npm install axios

然后,使用axios ,我们获取我们的数据:

const React = require('react')
const { useState, useEffect } = React;
const { Box, Text, Newline } = require('ink')
const axios = require('axios')

const url = 'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin%2Clitecoin%2Cmatic-network%2Cethereum%2Ctether%2Cbinancecoin%2Csolana%2Caave%2Ccardano%2Ctron&order=market_cap_desc&per_page=100&page=1&sparkline=false'

const Table = () => {

    const [data, setData] = useState([])

    useEffect(()=>{
        axios.get(url)
        .then(response => setData(response.data))
        .catch(e => console.log(e))
    },[])
    // Fetching data and catching possible errors

    return (
        <Box borderStyle='single' padding={2}>
            {
                data.length === 0 ?
                <Box>
                    <Text>Loading ...</Text>
                </Box> :
                <Box flexDirection='column'>
                    <Box>
                        <Box width='25%'><Text>COIN</Text></Box>
                        <Box width='25%'><Text>CURRENT PRICE (USD)</Text></Box>
                        <Box width='25%'><Text>24 HOUR CHANGE</Text></Box>
                        <Box width='25%'><Text>ALL TIME HIGH</Text></Box>
                    </Box>
                    <Newline/>
                    {
                        data.map(({id, name, current_price, price_change_percentage_24h, ath}) => (
                            <Box key={id}>
                                <Box width='25%'>
                                    <Text>{name}</Text>
                                </Box>
                                <Box width='25%'>
                                    <Text color='cyan'>{'$' + current_price.toLocaleString()}</Text>
                                </Box>
                                <Box width='25%'>
                                    <Text backgroundColor={Math.sign(price_change_percentage_24h) < 0 ? 'red' : 'green'}>
                                        {price_change_percentage_24h.toFixed(2) + '%'}
                                    </Text>
                                </Box>
                                <Box width='25%'>
                                    <Text color='green'>{'$' + ath.toLocaleString()}</Text>
                                </Box>
                            </Box>
                        ))
                    }
                </Box>
            }
        </Box>
    )
}

module.exports = Table;

而对于我们选择的硬币,我们应该看到下面的输出(由于加密货币市场的波动性,很可能有不同的值):

display

最终输出

总结

在本教程中,我们学习了如何用React和Ink建立一个命令行应用程序。

我们还使用了CoinGecko API和Axios来获取我们的数据。

Ink提供了更多的组件,你可以以多种方式组合它,以创建真正有用的命令行程序。

谢谢你的阅读,我很快就会看到你。