Возможно, вы слышали, что код внешнего компонента может выполняться в браузере, в мобильном приложении или даже непосредственно на различных устройствах, но видели ли вы это раньше: внешний компонент запускается непосредственно в окне командной строки, что позволяет front-end Код строит графический интерфейс и логику взаимодействия с окном терминала?
Сегодня я хотел бы поделиться с вами очень интересным проектом с открытым исходным кодом: ink. Его роль заключается в отображении компонентов React в окне терминала, представляя окончательный интерфейс командной строки.
Эта статья посвящена реальному бою.В начале я познакомлю вас с основным использованием, а затем я проведу практический учебный проект, основанный на реальных сценариях.
Начиная
При первом запуске рекомендуется использовать официальные леса для создания проекта, чтобы сэкономить время и нервы.
npx create-ink-app --typescript
Затем запустите этот фрагмент кода:
import React, { useState, useEffect } from 'react'
import { render, Text} from 'ink'
const Counter = () => {
const [count, setCount] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setCount(count => ++count)
}, 100)
return () => {
clearInterval(timer)
}
})
return (
<Text color="green">
{count} tests passed
</Text>
)
}
render(<Counter />);
Появится следующий интерфейс:
И цифры продолжают расти! Демонстрация небольшая, но достаточная, чтобы проиллюстрировать проблему:
-
Во-первых, эти текстовые выводы не консольны напрямую, а через
React 组件
предоставлено. -
Реагировать компоненты
状态管理
так же какhooks 逻辑
Он по-прежнему работает в графическом интерфейсе в командной строке.
То есть возможности интерфейса были расширены до окна командной строки, что, несомненно, является очень страшной способностью. Известный инструмент для создания документовGatsby
, инструмент управления пакетамиyarn2
Все используют эту возможность для завершения построения графического интерфейса терминала.
Инструмент командной строки
Возможно, вы только что узнали об этом инструменте и его назначении, но еще мало знакомы с тем, как его использовать. Далее, давайте воспользуемся практическим примером для ведения реального боя и быстро с ним ознакомимся. Репозиторий кода был загружен на git, вы можете разветвить код по этому адресу:GitHub.com/ternary0704…
Теперь давайте разработаем этот проект от начала до конца.
Предыстория проекта
Прежде всего, поговорим о предыстории проекта.В бизнес-проекте TS мы однажды столкнулись с проблемой: из-заproduction
В режиме мы используем первыйtsc
, получите код продукта js, а затем используйтеwebpack
Упакуйте эти продукты.
Но ошибка сообщается непосредственно при построении, причина в том, чтоtsc
невозможноts(x)
Другие файлы ресурсов перемещаются в каталог продукта, поэтому, когда веб-пакет упаковывает продукт, он обнаруживает, что некоторые файлы ресурсов вообще не могут быть найдены! Например, путь такой картинки до этого такой -src/asset/1.png
, но они есть в каталоге товаровdist
Но его нет, поэтому, когда вебпак запаковывает код в каталог dist, он обнаружит, что этого образа не существует, поэтому сообщает об ошибке.
Решения
Как это решить?
Очевидно, что расширить возможности tsc сложно, и сейчас лучше всего написать скрипт вручную.src
Скопируйте все файлы ресурсов ниже вdist
каталог, который решает проблему невозможности найти ресурс.
1. Логика копирования файла
После определения решения записываем следующий ts-код:
import { join, parse } from "path";
import { fdir } from 'fdir';
import fse from 'fs-extra'
const staticFiles = await new fdir()
.withFullPaths()
// 过滤掉 node_modules、ts、tsx
.filter(
(p) =>
!p.includes('node_modules') &&
!p.endsWith('.ts') &&
!p.endsWith('.tsx')
)
// 搜索 src 目录
.crawl(srcPath)
.withPromise() as string[]
await Promise.all(staticFiles.map(file => {
const targetFilePath = file.replace(srcPath, distPath);
// 创建目录并拷贝文件
return fse.mkdirp(parse(targetFilePath).dir)
.then(() => fse.copyFile(file, distPath))
);
}))
код используетfdir
Эта библиотека ищет только файлы.Это очень простая в использовании библиотека.Метод написания также очень элегантный.Рекомендуется всем использовать ее.
Мы выполнили эту логику и успешно перенесли файл ресурсов в каталог продукта.
Проблема решается, но мы можем ли мы инкапсулировать эту логику, чтобы облегчить повторное использование в других проектах или даже предоставлять ему непосредственно другим для повторного использования?
Затем я подумал об инструментах командной строки.
Во-вторых, построение графического интерфейса командной строки.
Затем мы используемink
, то есть использование компонентов React для создания графического интерфейса командной строки Код корневого компонента выглядит следующим образом:
// index.tsx 引入代码省略
interface AppProps {
fileConsumer: FileCopyConsumer
}
const ACTIVE_TAB_NAME = {
STATE: "执行状态",
LOG: "执行日志"
}
const App: FC<AppProps> = ({ fileConsumer }) => {
const [activeTab, setActiveTab] = useState<string>(ACTIVE_TAB_NAME.STATE);
const handleTabChange = (name) => {
setActiveTab(name)
}
const WELCOME_TEXT = dedent`
欢迎来到 \`ink-copy\` 控制台!功能概览如下(按 **Tab** 切换):
`
return <>
<FullScreen>
<Box>
<Markdown>{WELCOME_TEXT}</Markdown>
</Box>
<Tabs onChange={handleTabChange}>
<Tab name={ACTIVE_TAB_NAME.STATE}>{ACTIVE_TAB_NAME.STATE}</Tab>
<Tab name={ACTIVE_TAB_NAME.LOG}>{ACTIVE_TAB_NAME.LOG}</Tab>
</Tabs>
<Box>
<Box display={ activeTab === ACTIVE_TAB_NAME.STATE ? 'flex': 'none'}>
<State />
</Box>
<Box display={ activeTab === ACTIVE_TAB_NAME.LOG ? 'flex': 'none'}>
<Log />
</Box>
</Box>
</FullScreen>
</>
};
export default App;
Как видите, есть два основных компонента:State
а такжеLog
, соответствующие двум столбцам Tab соответственно. Для получения конкретного кода вы можете обратиться к складу.Визуализация показана ниже:
3. Как графический интерфейс отображает бизнес-статус в режиме реального времени?
Теперь проблема, логика работы с файлами была разработана, а также построен GUI-интерфейс. Итак, как теперь совместить их, то есть как графический интерфейс отображает состояние операций с файлами в реальном времени?
В связи с этим нам необходимо ввести третью сторону для связи между этими двумя модулями. В частности, мы поддерживаем объект EventBus в логике файловых операций, а затем передаем этот EventBus через Context в компоненте React. На этом связь между пользовательским интерфейсом и модулем файловых операций завершена.
Теперь давайте разработаем этот объект EventBus, который выглядит следующим образом:FileCopyConsumer
:
export interface EventData {
kind: string;
payload: any;
}
export class FileCopyConsumer {
private callbacks: Function[];
constructor() {
this.callbacks = []
}
// 供 React 组件绑定回调
onEvent(fn: Function) {
this.callbacks.push(fn);
}
// 文件操作完成后调用
onDone(event: EventData) {
this.callbacks.forEach(callback => callback(event))
}
}
Затем в модуле файловых операций и модуле пользовательского интерфейса нам нужно адаптировать ответ.Сначала давайте посмотрим на модуль файловых операций и сделаем инкапсуляцию.
export class FileOperator {
fileConsumer: FileCopyConsumer;
srcPath: string;
targetPath: string;
constructor(srcPath ?: string, targetPath ?: string) {
// 初始化 EventBus 对象
this.fileConsumer = new FileCopyConsumer();
this.srcPath = srcPath ?? join(process.cwd(), 'src');
this.targetPath = targetPath ?? join(process.cwd(), 'dist');
}
async copyFiles() {
// 存储 log 信息
const stats = [];
// 在 src 中搜索文件
const staticFiles = ...
await Promise.all(staticFiles.map(file => {
// ...
// 存储 log
.then(() => stats.push(`Copied file from [${file}] to [${targetFilePath}]`));
}))
// 调用 onDone
this.fileConsumer.onDone({
kind: "finish",
payload: stats
})
}
}
а затем инициализироватьFileOperator
После этогоfileConsumer
Передайте его в компонент через React Context, чтобы компонент мог получить к нему доступfileConsumer
, а затем вы можете привязать функцию обратного вызова.Код демонстрируется следующим образом:
// 组件当中拿到 fileConsumer & 绑定回调
export const State: FC<{}> = () => {
const context = useContext(Context);
const [finish, setFinish] = useState(false);
context?.fileConsumer.onEvent((data: EventData) => {
// 下面的逻辑在文件拷贝完成后执行
if (data.kind === 'finish') {
setTimeout(() => {
setFinish(true)
}, 2000)
}
})
return
//(JSX代码)
}
Таким образом, мы успешно соединили логику пользовательского интерфейса и работы с файлами. Конечно, из-за нехватки места некоторые коды все еще не показаны, а полные коды находятся в репозитории git. Надеюсь, каждый сможет раскошелиться и испытать на себе дизайн всего проекта.
В целом, тот факт, что код компонента React может работать в терминале командной строки, действительно захватывающая вещь, которая освобождает больше места для воображения для внешнего интерфейса. Использование этой способности в этой статье — лишь верхушка айсберга. Другие позы ждут вас, чтобы разблокировать, так что идите и играйте!
Эта статья была впервые опубликована в публичном аккаунте «Front-end Sanyuan Classmates», и все желающие могут обратить на это внимание.Оригинальная ссылка:вау руб! На самом деле он поместил рендеринг компонента React в окно терминала командной строки внутри!
Команде архитектуры внешнего интерфейса ByteDance IES срочно нужны таланты (p5/p6/p7 много HC), приглашаю добавить меня WeChat sanyuan0704 для общения и приглашаю всех к совместной работе.