DUICUO

彼は実際に、コマンドラインターミナルウィンドウで React コンポーネントを実行しました。

[[384219]]

フロントエンド コンポーネント コードはブラウザー、モバイル アプリ、またはさまざまなデバイスで直接実行できることは聞いたことがあるかもしれませんが、フロントエンド コンポーネントがコマンド ライン ウィンドウで直接実行され、フロントエンド コードがターミナル ウィンドウの GUI インターフェイスと対話ロジックを構築できるのを見たことがありますか?

今日は、非常に興味深いオープンソースプロジェクト「ink」についてご紹介します。このプロジェクトの目的は、Reactコンポーネントをターミナルウィンドウにレンダリングし、最終的なコマンドラインインターフェースを提供することです。

この記事では、実践的な応用に焦点を当てています。まず基本的な使い方を説明し、その後、実際のシナリオに基づいた実践的なプロジェクトに取り組みます。

第一印象

始めたばかりのときは、時間と労力を節約できるため、公式のスキャフォールディングを使用してプロジェクトを作成することをお勧めします。

  1. npx作成-ink-app --typescript  

次に、次のコードを実行します。

  1. Reactをインポートし、{useState、useEffect}  「反応する」  
  2. インポート { レンダリング, テキスト}から  'インク'  
  3.  
  4. const カウンター = () => {
  5. const [ count , setCount] = useState(0)
  6. 使用効果(() => {
  7. 定数タイマー = setInterval(() => {
  8. setCount(カウント=> ++カウント)
  9. }, 100)
  10. 戻り値() => {
  11. clearInterval(タイマー)
  12. }
  13.      
  14. })
  15.  
  16. 戻る
  17. <テキストの色= "緑" >
  18. { count } 件のテストが合格しました
  19. </テキスト>
  20. }
  21.  
  22. レンダリング(<カウンター />);

次のインターフェースが表示されます。


そして、その数は増え続けています!デモは短いですが、要点を説明するには十分です。

  1. まず、これらのテキスト出力は直接コンソール化されるのではなく、React コンポーネントを通じてレンダリングされます。
  2. React コンポーネントの状態管理とフック ロジックは、コマンド ライン GUI に配置した場合でも機能します。

言い換えれば、フロントエンドの機能がコマンドラインウィンドウにまで拡張されたということです。これは紛れもなく強力な機能です。Gatsbyのような有名なドキュメント生成ツールやyarn2のようなパッケージ管理ツールは、どちらもこの機能を利用してターミナルGUIを構築しています。

コマンドラインツールプロジェクトの実践

このツールとその用途については既にご存知かもしれませんが、具体的な使い方がまだよくわからないという方もいらっしゃるかもしれません。具体的な例を使って、すぐに使い方に慣れていきましょう。コードリポジトリはGitにアップロードされています。こちらのアドレスからフォークできます:https://github.com/sanyuan0704/ink-copy-command

今からこのプロジェクトを最初から最後まで開発していきます。

プロジェクトの背景

まず、プロジェクトの背景をご説明いたします。TypeScriptのビジネスプロジェクトで、ある問題が発生しました。本番環境では、まずTSCを使ってJS出力コードを取得し、次にWebpackを使ってこれらの出力をバンドルしていました。

しかし、tscがts(x)以外のリソースファイルをアーティファクトディレクトリに移動できなかったため、ビルドプロセス中にエラーが発生しました。その結果、webpackはアーティファクトをバンドルする際に一部のリソースファイルが見つからないことを検出しました。例えば、src/asset/1.pngというパスの画像が存在しましたが、アーティファクトディレクトリdistには存在しませんでした。そのため、webpackがdistディレクトリ内のコードをバンドルする際に、画像が見つからないことが検出され、エラーが発生していました。

解決

それで、これをどうやって解決すればいいのでしょうか?

tsc の機能を拡張するのは明らかに困難です。現時点での最善の方法は、src ディレクトリ以下のすべてのリソースファイルを dist ディレクトリに手動でコピーするスクリプトを作成することです。これにより、リソースが見つからないという問題は解決します。

I. ファイルコピーロジック

解決方法を決定した後、次の TypeScript コードを作成しました。

  1. インポート { join , parse }から  "パス" ;
  2. インポート { fdir }から  'fdir' ;
  3. fseインポート  'fs-extra'  
  4. const staticFiles = 新しい fdir() を待つ
  5. .withFullPaths()
  6. // node_modules、ts、tsxを除外する
  7. 。フィルター(
  8. (p) =>
  9. !p.includes( 'node_modules' ) &&
  10. !p.endsWith( '.ts' ) &&
  11. !p.endsWith( '.tsx' )
  12. // srcディレクトリを検索
  13. .crawl(ソースパス)
  14. .withPromise() を文字列[]として
  15.  
  16. Promise を待機します。すべて(staticFiles.map(file => {
  17. const targetFilePath = file.replace (srcPath、distPath);
  18. // ディレクトリを作成し、ファイルをコピーする
  19. fse.mkdirp(parse(targetFilePath).dir)を返します
  20. . then (() => fse.copyFile(file, distPath))
  21. );
  22. }))

このコードは、ファイル検索にfdirライブラリを使用しています。これは非常に便利なライブラリで、構文も洗練されているので、どなたにもお勧めします。

このロジックを実行し、リソース ファイルをアーティファクト ディレクトリに正常に移動しました。

問題は解決しましたが、このロジックをカプセル化して他のプロジェクトで再利用しやすくしたり、再利用のために他のプロジェクトに直接提供したりすることはできますか?

そこで、コマンドラインツールを思いつきました。

II. コマンドラインGUIの設定

次に、Reactコンポーネントを使用する`ink`を使ってコマンドラインGUIを構築します。ルートコンポーネントのコードは次のとおりです。

  1. // index.tsx をインクルードするコードは省略されています
  2. インターフェースAppProps {
  3. ファイルコンシューマー: ファイルコピーコンシューマー
  4. }
  5.  
  6. 定数ACTIVE_TAB_NAME = {
  7. 状態: 「実行ステータス
  8. ログ: 「実行ログ」  
  9. }
  10.  
  11. const App: FC<AppProps> = ({ fileConsumer }) => {
  12. const [activeTab, setActiveTab] = useState<文字列>(ACTIVE_TAB_NAME.STATE);
  13. const handleTabChange = (名前) => {
  14. setActiveTab(名前)
  15. }
  16. const WELCOME_TEXT = dedent`
  17. `ink-copy` コンソールへようこそ!機能の概要は次のとおりです(**Tab** で切り替えます)。
  18. `
  19.  
  20. 戻る<>
  21. <フルスクリーン>
  22. <ボックス>
  23. <マークダウン>{WELCOME_TEXT} </マークダウン>
  24. </ボックス>
  25. <タブ onChange={handleTabChange}>
  26. <タブ={ACTIVE_TAB_NAME.STATE}>{ACTIVE_TAB_NAME.STATE}</タブ>
  27. <タブ={ACTIVE_TAB_NAME.LOG}>{ACTIVE_TAB_NAME.LOG}</タブ>
  28. </タブ>
  29. <ボックス>
  30. <ボックス display={ activeTab === ACTIVE_TAB_NAME.STATE ? 'flex' : 'none' }>
  31. <状態 />
  32. </ボックス>
  33. <ボックス display={ activeTab === ACTIVE_TAB_NAME.LOG ? 'flex' : 'none' }>
  34. <ログ />
  35. </ボックス>
  36. </ボックス>
  37. </フルスクリーン>
  38. </>
  39. };
  40.  
  41. デフォルトアプリをエクスポートします。

ご覧の通り、これは主に「状態」と「ログ」という2つのコンポーネントで構成されており、それぞれ別のタブに対応しています。具体的なコードについてはリポジトリをご覧ください。以下はスクリーンショットです。



III. GUI で業務状況をリアルタイムに表示するにはどうすればよいですか?

ここで問題が生じます。ファイル操作ロジックが開発され、GUIインターフェースが構築されました。では、この2つをどのように組み合わせれば良いのでしょうか?言い換えれば、GUIでファイル操作のリアルタイムステータスをどのように表示すれば良いのでしょうか?

これに対処するには、これら2つのモジュール間の通信を容易にするサードパーティ製のメカニズムを導入する必要があります。具体的には、ファイル操作ロジック内にEventBusオブジェクトを保持し、このEventBusをContext経由でReactコンポーネントに渡します。これにより、UIとファイル操作モジュール間の通信が可能になります。

ここで、この EventBus オブジェクト、具体的には以下の FileCopyConsumer を開発してみましょう。

  1. エクスポートインターフェースEventData {
  2. 種類: 文字列;
  3. ペイロード:任意;
  4. }
  5.  
  6. エクスポートクラスFileCopyConsumer {
  7.  
  8. プライベートコールバック: Function [];
  9. コンストラクター() {
  10. this.callbacks = []
  11. }
  12. // Reactコンポーネントのコールバックを提供します
  13. onEvent(fn:関数) {
  14. this.callbacks.push(fn);
  15. }
  16. // ファイル操作が完了した後に呼び出されます
  17. onDone(イベント: イベントデータ) {
  18. this.callbacks.forEach(コールバック => コー​​ルバック(イベント))
  19. }
  20. }

次に、ファイル操作モジュールとUIモジュールに適切な調整を加える必要があります。まずはファイル操作モジュールを見て、それをカプセル化してみましょう。

  1. エクスポートクラスFileOperator {
  2. ファイルコンシューマー: FileCopyConsumer;
  3. srcPath: 文字列;
  4. targetPath: 文字列;
  5. コンストラクター(srcPath ?: 文字列, targetPath ?: 文字列) {
  6. // EventBusオブジェクトを初期化する
  7. this.fileConsumer = 新しい FileCopyConsumer();
  8. this.srcPath = srcPath ?? join (process.cwd(), 'src' );
  9. this.targetPath = targetPath ?? join (process.cwd(), 'dist' );
  10. }
  11.  
  12. 非同期コピーファイル() {
  13. // ログ情報を保存する
  14. 定数統計 = [];
  15. // src内のファイルを検索する
  16. const staticFiles = ...
  17.      
  18. Promise を待機します。すべて(staticFiles.map(file => {
  19. // ...
  20. // ログを保存する
  21. . then (() => stats.push(` [${file}]から[${targetFilePath}]ファイルをコピーしました`));
  22. }))
  23. // onDone を呼び出す
  24. this.fileConsumer.onDone({
  25. 種類: "仕上げ"
  26. ペイロード: 統計
  27. })
  28. }
  29. }

FileOperatorを初期化した後、React Contextを介してfileConsumerがコンポーネントに渡されます。これにより、コンポーネントはfileConsumerにアクセスし、コールバック関数をバインドできるようになります。コードデモは以下の通りです。

  1. // fileConsumer を取得し、コンポーネント内のコールバックをバインドします
  2. エクスポートconst状態: FC<{}> = () => {
  3. const コンテキスト = useContext(コンテキスト);
  4. const [終了、setFinish] = useState( false );
  5. コンテキスト?.fileConsumer.onEvent((データ: EventData) => {
  6. // ファイルのコピーが完了すると、次のロジックが実行されます。
  7. if (data.kind === 'finish' ) {
  8. setTimeout(() => {
  9. setFinish( true )
  10. }, 2000)
  11. }
  12. })
  13.  
  14. 戻る  
  15. //(JSXコード)
  16. }

このようにして、UIとファイル操作ロジックの連携に成功しました。もちろん、スペースの都合上、一部のコードはここには掲載していませんが、完全なコードはGitリポジトリにあります。ぜひフォークして、プロジェクト全体の設計を体験してみてください。

総じて、Reactコンポーネントコードをコマンドラインターミナルで実行できる機能は非常に魅力的で、フロントエンド開発の可能性をさらに広げてくれます。この記事ではこの機能のほんの一部を紹介したに過ぎません。他にも多くの活用方法が見つかるはずですので、ぜひ探索してみてください!