在命令行裏也能用 React

這是我參與8月更文挑戰的第1天,活動詳情查看:8月更文挑戰html

用過 React 的同窗都知道,React 做爲一個視圖庫,在進行 Web 開發的時候須要安裝兩個模塊。node

npm install react --save
npm install react-dom --save
複製代碼

react 模塊主要提供了組件的生命週期、虛擬 DOM Diff、Hooks 等能力,以及將 JSX 轉換爲虛擬 DOM 的 h 方法。而 react-dom 主要對外暴露一個 render 方法,將虛擬 DOM 轉化爲真實 DOM。react

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-domreact-native ,甚至能夠渲染到命令行上,這也是咱們今天介紹的 ink算法

🔗 npm ink: www.npmjs.com/package/rea…typescript

Ink

ink 內部使用 facebook 基於 C++ 開發的一款跨平臺渲染引擎 yoga,支持 Flex 佈局,功能十分強大。另外,React Native 內部使用了該引擎。npm

初始化

這裏有一個官方提供的腳手架,咱們能夠直接經過這個腳手架來建立一個項目。react-native

$ mkdir ink-app
$ cd ink-app
$ npx create-ink-app
複製代碼

若是你想使用 TypeScript 來編寫項目,你也可使用以下命令:瀏覽器

$ npx create-ink-app --typescript
複製代碼

生成的代碼以下:bash

// src/cli.js
#!/usr/bin/env node
const 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.js
const App = (props) => (
  <Text> Hello, <Text color = "green"> { props.name || 'UserName' } </Text> </Text>
)

module.exports = App;
複製代碼

除了 inkreact,腳手架項目還引入了 meowimport-jsx 兩個庫。babel

meow 的主要做用是運行命令時,對參數進行解析,將解析的參數放到 flags 屬性中,其做用與 yargscommander 同樣,是構建 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.js
const component = (props) => (
  <Text> Hello, <Text color = "green"> { props.name || 'UserName' } </Text> </Text>
)

// cli.js
const 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 項目中,自己對性能要求也沒那麼高,經過這種方式,也能更快速的進行項目搭建。

內置組件

因爲是非瀏覽器的運行環境,inkreact-native 同樣提供了內置的一些組件,用於渲染終端中的特定元素。

<Text>

<Text> 組件用於在終端渲染文字,能夠爲文字指定特定的顏色、加粗、斜體、下劃線、刪除線等等。

DEMO:

// ui.js
const 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.js
const 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 中 marginpaddingborder 屬性外,還能支持 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:www.npmjs.com/package/ink…

<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:www.npmjs.com/package/ink…

下面經過一個 DEMO,展現其具體的使用方式,在終端上記錄用戶的全部輸出,若是按下的是刪除鍵,則刪除最近記錄的一個字符。

const React = require('react')
const { useInput, Text } = require('ink')

const { useState } = React
module.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 } = React
const 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 } = React
module.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 } = React
module.exports = () => {
  const { write } = useStdout()
  useEffect(() => {
    // 在終端進行寫入
		write('Hello from Ink to stdout')
	}, [])
  return null
}
複製代碼

第三方組件

除了內置的這些組件和 Hooks 外,還有豐富的第三方生態。好比:Loading組件、超連接組件、表格組件、高亮組件、多選組件、圖片組件……

🔗 ink#第三方組件:www.npmjs.com/package/ink…

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 工具須要實現,能夠考慮這款工具,至少沒必要煩惱如何在命令行進行文本對齊。

相關文章
相關標籤/搜索