自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

他居然把 React 組件跑在命令行終端窗口里面!

開(kāi)源
今天, 給大家分享一個(gè)非常有意思的開(kāi)源項(xiàng)目: ink。它的作用就是將 React 組件渲染在終端窗口中,呈現(xiàn)出最后的命令行界面。

[[384219]]

 也許你之前聽(tīng)說(shuō)過(guò)前端組件代碼可以運(yùn)行在瀏覽器,運(yùn)行在移動(dòng)端 App 里面,甚至可以直接在各種設(shè)備當(dāng)中,但你有沒(méi)有見(jiàn)過(guò): 前端組件直接跑在命令行窗口里面,讓前端代碼構(gòu)建出終端窗口的 GUI 界面和交互邏輯?

今天, 給大家分享一個(gè)非常有意思的開(kāi)源項(xiàng)目: ink。它的作用就是將 React 組件渲染在終端窗口中,呈現(xiàn)出最后的命令行界面。

本文偏重實(shí)戰(zhàn),前面會(huì)帶大家熟悉基本使用,然后會(huì)做一個(gè)基于實(shí)際場(chǎng)景的實(shí)戰(zhàn)項(xiàng)目。

上手初體驗(yàn)

剛開(kāi)始上手時(shí),推薦使用官方的腳手架創(chuàng)建項(xiàng)目,省時(shí)省心。

  1. npx create-ink-app --typescript 

然后運(yùn)行這樣一段代碼:

  1. import React, { useState, useEffect } from 'react' 
  2. import { render, Text} from 'ink' 
  3.  
  4. const Counter = () => { 
  5.   const [count, setCount] = useState(0) 
  6.   useEffect(() => { 
  7.     const timer = setInterval(() => { 
  8.       setCount(count => ++count
  9.     }, 100) 
  10.     return () => { 
  11.       clearInterval(timer) 
  12.     } 
  13.      
  14.   }) 
  15.  
  16.   return ( 
  17.     <Text color="green"
  18.       {count} tests passed 
  19.     </Text> 
  20.   ) 
  21.  
  22. render(<Counter />); 

會(huì)出現(xiàn)如下的界面:


并且數(shù)字一直遞增!demo 雖小,但足以說(shuō)明問(wèn)題:

  1. 首先,這些文本輸出都不是直接 console 出來(lái)的,而是通過(guò) React 組件渲染出來(lái)的。
  2. React 組件的狀態(tài)管理以及hooks 邏輯放到命令行的 GUI 當(dāng)中仍然是生效的。

也就是說(shuō),前端的能力以及擴(kuò)展到了命令行窗口當(dāng)中了,這無(wú)疑是一項(xiàng)非??膳碌哪芰?。著名的文檔生成工具Gatsby,包管理工具yarn2都使用了這項(xiàng)能力來(lái)完成終端 GUI 的搭建。

命令行工具項(xiàng)目實(shí)戰(zhàn)

可能大家剛剛了解到這個(gè)工具,知道它的用途,但對(duì)于具體如何使用還是比較陌生。接下來(lái)讓我們以一個(gè)實(shí)際的例子來(lái)進(jìn)行實(shí)戰(zhàn),快速熟悉。代碼倉(cāng)庫(kù)已經(jīng)上傳到 git,大家可以這個(gè)地址下面 fork 代碼: https://github.com/sanyuan0704/ink-copy-command。

下面我們就來(lái)從頭到尾開(kāi)發(fā)這個(gè)項(xiàng)目。

項(xiàng)目背景

首先說(shuō)一說(shuō)項(xiàng)目的產(chǎn)生背景,在一個(gè) TS 的業(yè)務(wù)項(xiàng)目當(dāng)中,我們?cè)?jīng)碰到了一個(gè)問(wèn)題:由于production模式下面,我們是采用先 tsc,拿到 js 產(chǎn)物代碼,再用webpack打包這些產(chǎn)物。

但構(gòu)建的時(shí)候直接報(bào)錯(cuò)了,原因就是 tsc 無(wú)法將 ts(x) 以外的資源文件移動(dòng)到產(chǎn)物目錄,以至于 webpack 在對(duì)于產(chǎn)物進(jìn)行打包的時(shí)候,發(fā)現(xiàn)有些資源文件根本找不到!比如以前有這樣一張圖片的路徑是這樣—— src/asset/1.png,但這些在產(chǎn)物目錄dist卻沒(méi)還有,因此 webpack 在打包 dist 目錄下的代碼時(shí),會(huì)發(fā)現(xiàn)這張圖片不存在,于是報(bào)錯(cuò)了。

解決思路

那如何來(lái)解決呢?

很顯然,我們很難去擴(kuò)展 tsc 的能力,現(xiàn)在最好的方式就是寫(xiě)個(gè)腳本手動(dòng)將src下面的所有資源文件一一拷貝到dist目錄,這樣就能解決資源無(wú)法找到的問(wèn)題。

一、拷貝文件邏輯

確定了解決思路之后,我們寫(xiě)下這樣一段 ts 代碼:

  1. import { join, parse } from "path"
  2. import { fdir } from 'fdir'
  3. import fse from 'fs-extra' 
  4. const staticFiles = await new fdir()  
  5.   .withFullPaths()    
  6.   // 過(guò)濾掉 node_modules、ts、tsx 
  7.   .filter( 
  8.     (p) => 
  9.       !p.includes('node_modules') && 
  10.       !p.endsWith('.ts') && 
  11.       !p.endsWith('.tsx'
  12.   ) 
  13.   // 搜索 src 目錄 
  14.   .crawl(srcPath) 
  15.   .withPromise() as string[] 
  16.  
  17. await Promise.all(staticFiles.map(file => { 
  18.   const targetFilePath = file.replace(srcPath, distPath); 
  19.   // 創(chuàng)建目錄并拷貝文件 
  20.   return fse.mkdirp(parse(targetFilePath).dir) 
  21.     .then(() => fse.copyFile(file, distPath)) 
  22.    ); 
  23. })) 

代碼使用了fdir這個(gè)庫(kù)才搜索文件,非常好用的一個(gè)庫(kù),寫(xiě)法上也很優(yōu)雅,推薦大家使用。

我們執(zhí)行這段邏輯,成功將資源文件轉(zhuǎn)移到到了產(chǎn)物目錄中。

問(wèn)題是解決掉了,但我們能不能封裝一下這個(gè)邏輯,讓它能夠更方便地在其它項(xiàng)目當(dāng)中復(fù)用,甚至直接提供給其他人復(fù)用呢?

接著,我想到了命令行工具。

二、命令行 GUI 搭建

接著我們使用 ink,也就是用 React 組件的方式來(lái)搭建命令行 GUI,根組件代碼如下:

  1. // index.tsx 引入代碼省略 
  2. interface AppProps { 
  3.  fileConsumer: FileCopyConsumer 
  4.  
  5. const ACTIVE_TAB_NAME = { 
  6.  STATE: "執(zhí)行狀態(tài)"
  7.  LOG: "執(zhí)行日志" 
  8.  
  9. const App: FC<AppProps> = ({ fileConsumer }) => { 
  10.  const [activeTab, setActiveTab] = useState<string>(ACTIVE_TAB_NAME.STATE); 
  11.  const handleTabChange = (name) => { 
  12.   setActiveTab(name
  13.  } 
  14.  const WELCOME_TEXT = dedent` 
  15.   歡迎來(lái)到 \`ink-copy\` 控制臺(tái)!功能概覽如下(按 **Tab** 切換): 
  16.  ` 
  17.  
  18.  return <> 
  19.    <FullScreen> 
  20.     <Box> 
  21.      <Markdown>{WELCOME_TEXT}</Markdown> 
  22.     </Box> 
  23.     <Tabs onChange={handleTabChange}> 
  24.      <Tab name={ACTIVE_TAB_NAME.STATE}>{ACTIVE_TAB_NAME.STATE}</Tab> 
  25.      <Tab name={ACTIVE_TAB_NAME.LOG}>{ACTIVE_TAB_NAME.LOG}</Tab> 
  26.     </Tabs> 
  27.     <Box> 
  28.      <Box display={ activeTab === ACTIVE_TAB_NAME.STATE ? 'flex''none'}> 
  29.       <State /> 
  30.      </Box> 
  31.      <Box display={ activeTab === ACTIVE_TAB_NAME.LOG ? 'flex''none'}> 
  32.       <Log /> 
  33.      </Box> 
  34.     </Box> 
  35.    </FullScreen> 
  36.  </> 
  37. }; 
  38.  
  39. export default App; 

可以看到,主要包含兩大組件: State和Log,分別對(duì)應(yīng)兩個(gè) Tab 欄。具體的代碼大家去參考倉(cāng)庫(kù)即可,下面放出效果圖:



三. GUI 如何實(shí)時(shí)展示業(yè)務(wù)狀態(tài)?

現(xiàn)在問(wèn)題就來(lái)了,文件操作的邏輯開(kāi)發(fā)完了,GUI 界面也搭建好了。那么現(xiàn)在如何將兩者結(jié)合起來(lái)呢,也就是 GUI 如何實(shí)時(shí)地展示文件操作的狀態(tài)呢?

對(duì)此,我們需要引入第三方,來(lái)進(jìn)行這兩個(gè)模塊的通信。具體來(lái)講,我們?cè)谖募僮鞯倪壿嬛芯S護(hù)一個(gè) EventBus 對(duì)象,然后在 React 組件當(dāng)中,通過(guò) Context 的方式傳入這個(gè) EventBus。從而完成 UI 和文件操作模塊的通信。

現(xiàn)在我們開(kāi)發(fā)一下這個(gè) EventBus 對(duì)象,也就是下面的FileCopyConsumer:

  1. export interface EventData { 
  2.   kind: string; 
  3.   payload: any
  4.  
  5. export class FileCopyConsumer { 
  6.  
  7.   private callbacks: Function[]; 
  8.   constructor() { 
  9.     this.callbacks = [] 
  10.   } 
  11.   // 供 React 組件綁定回調(diào) 
  12.   onEvent(fn: Function) { 
  13.     this.callbacks.push(fn); 
  14.   } 
  15.   // 文件操作完成后調(diào)用 
  16.   onDone(event: EventData) { 
  17.     this.callbacks.forEach(callback => callback(event)) 
  18.   } 

接著在文件操作模塊和 UI 模塊當(dāng)中,都需要做響應(yīng)的適配,首先看看文件操作模塊,我們做一下封裝。

  1. export class FileOperator { 
  2.   fileConsumer: FileCopyConsumer; 
  3.   srcPath: string; 
  4.   targetPath: string; 
  5.   constructor(srcPath ?: string, targetPath ?: string) { 
  6.     // 初始化 EventBus 對(duì)象 
  7.     this.fileConsumer = new FileCopyConsumer(); 
  8.     this.srcPath = srcPath ?? join(process.cwd(), 'src'); 
  9.     this.targetPath = targetPath ?? join(process.cwd(), 'dist'); 
  10.   } 
  11.  
  12.   async copyFiles() { 
  13.     // 存儲(chǔ) log 信息 
  14.     const stats = []; 
  15.     // 在 src 中搜索文件 
  16.     const staticFiles = ... 
  17.      
  18.     await Promise.all(staticFiles.map(file => { 
  19.         // ... 
  20.         // 存儲(chǔ) log 
  21.         .then(() => stats.push(`Copied file from [${file}] to [${targetFilePath}]`)); 
  22.     })) 
  23.     // 調(diào)用 onDone 
  24.     this.fileConsumer.onDone({ 
  25.       kind: "finish"
  26.       payload: stats 
  27.     }) 
  28.   } 

然后在初始化 FileOperator之后,將 fileConsumer通過(guò) React Context 傳入到組件當(dāng)中,這樣組件就能訪問(wèn)到fileConsumer,進(jìn)而可以進(jìn)行回調(diào)函數(shù)的綁定,代碼演示如下:

  1. // 組件當(dāng)中拿到 fileConsumer & 綁定回調(diào) 
  2. export const State: FC<{}> = () => { 
  3.   const context = useContext(Context); 
  4.   const [finish, setFinish] = useState(false); 
  5.   context?.fileConsumer.onEvent((data: EventData) => { 
  6.     // 下面的邏輯在文件拷貝完成后執(zhí)行 
  7.     if (data.kind === 'finish') { 
  8.       setTimeout(() => { 
  9.         setFinish(true
  10.       }, 2000) 
  11.     } 
  12.   }) 
  13.  
  14.   return  
  15.   //(JSX代碼) 

這樣,我們就成功地將 UI 和文件操作邏輯串聯(lián)了起來(lái)。當(dāng)然,篇幅所限,還有一些代碼并沒(méi)有展示出來(lái),完整的代碼都在 git 倉(cāng)庫(kù)當(dāng)中。希望大家能 fork 下來(lái)好好體會(huì)一下整個(gè)項(xiàng)目的設(shè)計(jì)。

總體來(lái)說(shuō),React 組件代碼能夠跑在命令行終端,確實(shí)是一件激動(dòng)人心的事情,給前端釋放了更多想象的空間。本文對(duì)于這個(gè)能力的使用也只是冰山一角,更多使用姿勢(shì)等待你去解鎖,趕緊去玩一玩吧!

 

責(zé)任編輯:姜華 來(lái)源: 前端三元同學(xué)
相關(guān)推薦

2021-07-29 09:07:44

React視圖庫(kù)Web 開(kāi)發(fā)

2022-01-04 09:02:24

瀏覽器命令行ttyd

2014-02-12 13:30:16

Linux命令行終端工具

2020-07-30 13:34:48

終端命令行Linux

2009-03-01 22:09:08

LinuxTerminal命令行終端

2023-03-28 08:40:22

命令行JSON用法

2011-09-05 15:09:07

Ubuntuw3m

2020-11-23 05:50:40

瀏覽器Web瀏覽器Linux

2019-12-09 09:23:04

Linux命令sort

2021-07-15 13:32:12

Linux生成密碼

2021-07-15 13:25:43

LinuxPDF

2013-01-29 14:08:58

UbuntuUbuntu手機(jī)Ubuntu手機(jī)操作系

2020-02-17 11:05:27

GitHub代碼開(kāi)發(fā)者

2023-02-26 01:28:09

終端命令行工具

2020-12-10 16:16:08

工具代碼開(kāi)發(fā)

2020-12-11 06:44:16

命令行工具開(kāi)發(fā)

2014-02-12 10:11:08

掃描病毒掃描Clam Antivi

2009-02-18 20:20:40

autojump更改目錄命令行下

2009-10-26 18:09:31

Oracle用戶(hù)解鎖

2009-05-30 09:26:38

AndroidGoogle移動(dòng)OS
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)