用过 React 的同学都晓得,React 作为一个视图库,在进行 Web 开发的时候须要装置两个模块。
npm install react --savenpm install react-dom --save
react
模块次要提供了组件的生命周期、虚构 DOM Diff、Hooks 等能力,以及将 JSX 转换为虚构 DOM 的 h
办法。而 react-dom
次要对外裸露一个 render
办法,将虚构 DOM 转化为实在 DOM。
import React from 'react'import ReactDOM from 'react-dom'/* import ReactDOM from 'react-dom/server' //服务的渲染 */class Hello extends React.component { render() { return <h1>Hello, world!</h1>, }}ReactDOM.render( <Hello />, document.getElementById('root'))
如果咱们将 react-dom
换成 react-native
就能够将虚构 DOM 转换为安卓或 iOS 的原生组件。我在之前的文章中介绍过,虚构 DOM 最大的劣势并不是其 Diff 算法,而是将 JSX 转换为对立的 DSL,通过其形象能力实现了跨平台的能力。除了官网提供的 react-dom
、react-native
,甚至能够渲染到命令行上,这也是咱们明天介绍的 ink
。
npm ink: https://www.npmjs.com/package...
ink
外部应用 facebook 基于 C++ 开发的一款跨平台渲染引擎 yoga
,反对 Flex 布局,性能非常弱小。另外,React Native 外部应用了该引擎。
初始化
这里有一个官网提供的脚手架,咱们能够间接通过这个脚手架来创立一个我的项目。
$ mkdir ink-app$ cd ink-app$ npx create-ink-app
如果你想应用 TypeScript 来编写我的项目,你也能够应用如下命令:
$ npx create-ink-app --typescript
生成的代码如下:
// src/cli.js#!/usr/bin/env nodeconst ink = require('ink')const meow = require('meow')const React = require('react')const importJsx = require('import-jsx')const ui = importJsx('./ui')const cli = meow(` Usage $ ink-cli Options --name Your name`)ink.render(React.createElement(ui, cli.flags))
// src/ui.jsconst App = (props) => ( <Text> Hello, <Text color = "green"> { props.name || 'UserName' } </Text> </Text>)module.exports = App;
除了 ink
和 react
,脚手架我的项目还引入了 meow
、import-jsx
两个库。
meow
的次要作用是运行命令时,对参数进行解析,将解析的参数放到 flags
属性中,其作用与 yargs
、commander
一样,是构建 CLI 工具的必备利器。
const meow = require('meow')// 传入的字符串,作为 help 信息。const cli = meow(` Options --name Your name --age Your age`)console.log('flags: ', cli.flags)
另一个 import-jsx
的次要作用,就是将 jsx
字符串转化为 createElement
办法的模式。
// ui.jsconst component = (props) => ( <Text> Hello, <Text color = "green"> { props.name || 'UserName' } </Text> </Text>)// cli.jsconst importJsx = require('import-jsx')const ui = importJsx('./ui')console.log(ui.toString()) // 输入转化后的后果
// 转化后果:props => /*#__PURE__*/React.createElement( Text, null, "Hello, ", /*#__PURE__*/React.createElement( Text, { color: "green" }, props.name || 'UserName' ))
这一步的工作个别由 babel 实现,如果咱们没有通过 babel 本义 jsx,应用 import-jsx
就相当于是运行时本义,对性能会有损耗。然而,在 CLI 我的项目中,自身对性能要求也没那么高,通过这种形式,也能更疾速的进行我的项目搭建。
内置组件
因为是非浏览器的运行环境,ink
与 react-native
一样提供了内置的一些组件,用于渲染终端中的特定元素。
\<Text\>
<Text>
组件用于在终端渲染文字,能够为文字指定特定的色彩、加粗、斜体、下划线、删除线等等。
DEMO:
// ui.jsconst React = require('react')const { Text } = require('ink')moudle.exports = () => (<> <Text>I am text</Text> <Text bold>I am bold</Text> <Text italic>I am italic</Text> <Text underline>I am underline</Text> <Text strikethrough>I am strikethrough</Text> <Text color="green">I am green</Text> <Text color="blue" backgroundColor="gray">I am blue on gray</Text></>)// cli.jsconst React = require('react')const importJsx = require('import-jsx')const { render } = require('ink')const ui = importJsx('./ui')render(React.createElement(ui))
其次要作用就是设置渲染到终端上的文本款式,有点相似于 HTML 中的 <font>
标签。
除了这种常见的 HTML 相干的文本属性,还反对比拟非凡的 wrap
属性,用于将溢出的文本进行截断。
长文本在超出终端的长度时,默认会进行换行解决。
<Text>loooooooooooooooooooooooooooooooooooooooong text</Text>
如果加上 wrap
属性,会对长文本进行截断。
<Text wrap="truncate"> loooooooooooooooooooooooooooooooooooooooong text</Text>
除了从尾部截断文本,还反对从文本两头和文本开始处进行截断。
<Text wrap="truncate"> loooooooooooooooooooooooooooooooooooooooong text</Text><Text wrap="truncate-middle"> loooooooooooooooooooooooooooooooooooooooong text</Text><Text wrap="truncate-start"> loooooooooooooooooooooooooooooooooooooooong text</Text>
\<Box\>
<Box>
组件用于布局,除了反对相似 CSS 中 margin
、padding
、border
属性外,还能反对 flex
布局,能够将 <Box>
了解为 HTML 中设置了 flex 布局的 div ( <div style="display: flex;">
)。
上面咱们先给一个 <Box>
组件设置高度为 10,而后主轴方向让元素两端对齐,穿插轴方向让元素位于底部对齐。
而后在给外部的两个 <Box>
组件设置一个 padding
和一个不同款式的边框。
const App = () => <Box height={10} alignItems="flex-end" justifyContent="space-between"> <Box borderStyle="double" borderColor="blue" padding={1} > <Text>Hello</Text> </Box> <Box borderStyle="classic" borderColor="red" padding={1} > <Text>World</Text> </Box></Box>
最终成果如下:
比拟非凡的属性是边框的款式: borderStyle
,和 CSS 提供的边框款式有点出入。
<Box borderStyle="single"> <Text>single</Text></Box><Box borderStyle="double"> <Text>double</Text></Box><Box borderStyle="round"> <Text>round</Text></Box><Box borderStyle="bold"> <Text>bold</Text></Box><Box borderStyle="singleDouble"> <Text>singleDouble</Text></Box><Box borderStyle="doubleSingle"> <Text>doubleSingle</Text></Box><Box borderStyle="classic"> <Text>classic</Text></Box>
<Box>
组件提供的其余属性和原生的 CSS 基本一致,具体介绍能够查阅其文档:
ink#Box:https://www.npmjs.com/package/ink#box
\<Newline\>
<NewLine>
组件相当于间接在终端中增加一个 \n
字符,用于换行(PS:只反对插入在 <Text>
元素之间);
const App = () => (<> <Text>Hello</Text> <Text>World</Text></>)
const App = () => (<> <Text>Hello</Text> <Newline /> <Text>World</Text></>)
\<Spacer\>
<Spacer>
组件用于隔开两个元素,应用后,会将距离开两个元素隔开到终端的两边,成果有点相似于 flex 布局的两端对齐(justify-content: space-between;
)
const App1 = () => <Box> <Text>Left</Text> <Spacer /> <Text>Right</Text></Box>;const App2 = () => <Box justifyContent="space-between"> <Text>Left</Text> <Text>Right</Text></Box>;
下面两段代码的表现形式统一:
内置 Hooks
ink
除了提供一些布局用的组件,还提供了一些 Hooks。
useInput
可用于监听用户的输出,useInput
承受一个回调函数,用户每次按下键盘的按键,都会调用 useInput
传入的回调,并传入两个参数。
useInput((input: string, key: Object) => void)
第一个参数:input ,示意按下按键对应的字符。第二个参数: key ,为一个对象,对应按下的一些功能键。
- 如果按下回车,
key.return = true
; - 如果按下删除键,
key.delete = true
; - 如果按下esc键,
key.escape = true
;
具体反对哪些性能按键,能够参考官网文档:
ink#useInput:https://www.npmjs.com/package/ink#useinputinputhandler-options
上面通过一个 DEMO,展现其具体的应用形式,在终端上记录用户的所有输入,如果按下的是删除键,则删除最近记录的一个字符。
const React = require('react')const { useInput, Text } = require('ink')const { useState } = Reactmodule.exports = () => { const [char, setChar] = useState('') useInput((input, key) => { if (key.delete) { // 按下删除键,删除一个字符 setChar(char.slice(0, -1)) return } // 追加最新按下的字符 setChar(char + input) }) return <Text>input char: {char}</Text>}
useApp
对外裸露一个 exit
办法,用于退出终端。
const React = require('react')const { useApp } = require('ink')const { useEffect } = Reactconst App = () => { const { exit } = useApp() // 3s 后退出终端 useEffect(() => { setTimeout(() => { exit(); }, 3000); }, []); return <Text color="red">3s 后退出终端……</Text>}
useStdin
用于获取命令行的输出流。这里用一个简略的案例,来模仿用户登录。
const React = require('react')const { useStdin } = require('ink')const { useState, useEffect } = Reactmodule.exports = () => { const [pwd, setPwd] = useState('') const { stdin } = useStdin() useEffect(() => { // 设置明码后,终止输出 if (pwd) stdin.pause() }, [pwd]) stdin.on('data', (data) => { // 提取 data,设置到 pwd 变量中 const value = data.toString().trim() setPwd(value) }) // pwd 为空时,提醒用户输出明码 if (!pwd) { return <Text backgroundColor="blue">password:</Text> } return pwd === 'hk01810' ? <Text color="green">登录胜利</Text> : <Text color="red">有内鬼,终止交易</Text>}
useStdout
用于获取命令行的输入流。会裸露 stdout
的写入流,还会裸露一个 write
办法,用于在终端进行输出。
const React = require('react')const { useStdout } = require('ink')const { useEffect } = Reactmodule.exports = () => { const { write } = useStdout() useEffect(() => { // 在终端进行写入 write('Hello from Ink to stdout') }, []) return null}
第三方组件
除了内置的这些组件和 Hooks 外,还有丰盛的第三方生态。比方:Loading组件、超链接组件、表格组件、高亮组件、多选组件、图片组件……
ink#第三方组件:https://www.npmjs.com/package/ink#useful-components
ink-spinner
ink-link
ink-table
ink-syntax-highlight
ink-muti-select
调试工具
ink 属于 React 生态,天然可能反对 React 官网提供的调试工具 React Devtools
。
$ npm install react-devtools # 装置调试工具
$ npx react-devtools # 启动调试工具
而后,在启动利用时,在后面设置 DEV
全局变量。
DEV=true node src/cli
运行后的成果如下:
总结
React 的确是视图开发的一把利器,再加上 Hooks 的加持,其形象能力失去了进一步的晋升,对立的 DSL 加上 虚构 DOM,照理来说,是能够在任何平台进行渲染的。甚至,微软官网都开发了一个 React Native for Windows
,要害是这个货色不仅仅能开发 Windows 的桌面软件,还能够开发 mac 的桌面软件。
有点跑题,说回 ink
,大家熟知的 Gatsby
的命令行工具也是通过 ink
进行开发的。如果大家后续有本地的 CLI 工具须要实现,能够思考这款工具,至多不用懊恼如何在命令行进行文本对齐。