Render React на встроенный ЖК-экран

внешний интерфейс React.js React Native
Render React на встроенный ЖК-экран

Мы все знаем, что одним из главных преимуществ React является универсальность «Учись один раз — пиши где угодно». Но как вы можете отображать пользовательский интерфейс с помощью React вне браузера или даже вне Node.js? В этой статье вы познакомитесь со встроенным уровнем драйверов с помощью React, позволяющим беспрепятственно интегрировать современные клиентские технологии с устаревшим оборудованием.

Общий обзор

На этот раз нашей целью рендеринга является матричный ЖК-экран размером всего 0,96 дюйма, модель — SSD1306. Это всего лишь 128x64, и вы, вероятно, использовали его для прокрутки текстов песен в первые дни черно-белых MP3. Насколько мал этот чип? Я сделал реальное сравнительное фото:

Обычный ПК явно не будет напрямую поддерживать такое железо, поэтому нужна встроенная среда разработки — я выбрал самый удобный Raspberry Pi.

Хотя в Raspberry Pi уже есть полноценная готовая языковая среда, такая как Python и Node.js, я надеюсь оспорить лимит и выбрать технологию по методу «запуска React на аппаратной среде минимальной конфигурации». Я ищу сверхлегкий интерпретатор JS для встраиваемого оборудования, который заменит более тяжелый V8 в браузерах и Node.js. я наконец выбралQuickJS, молодой, но известный движок JS.

Проще говоря, наша цель состоит в том, чтобыПройти четыре системы React → QuickJS → Raspberry Pi → чип SSD1306. Эту, казалось бы, трудную задачу можно разбить на следующие этапы:

  • Портирование React на встроенный JS-движок
  • Дисковое оборудование на основе языка C
  • Обертывание расширений языка C для движков JS
  • Реализация бэкенда рендеринга React

Хотя каждый вышеописанный шаг не сложен, его достаточно, чтобы написать независимый технический блог. Чтобы сохранить удобочитаемость, эта статья пытается охватить только основные понятия и ключевые шаги. Но я могу сначала заверить вас, что окончательный проект в целом не только достаточно прост в коде, но также является бесплатным и с открытым исходным кодом.

Давайте начнем!

Портирование React на встроенный JS-движок

На самом деле, QuickJS — не единственный встроенный движок JS.До этого в сообществе было много движков JS для оборудования IoT, таких как DukTape и XS, но они были прохладными. Напротив, в QuickJS меня больше всего привлекают следующие вещи:

  • Почти полная поддержка ES2019. От ES Module до async и Proxy, эти современные синтаксис JS, к которым мы привыкли, уже поддерживаются QuickJS и прошли тест Test262. Напротив, другие встроенные JS-движки могут даже недостаточно поддерживать ES6.
  • Легкий, гибкий и легко встраиваемый. Углубленное изучение двигателя V8 нравится многим фронтендистам, ведь собрать копию самостоятельно довольно сложно. В отличие от этого, QuickJS не имеет зависимостей и может быть скомпилирован с помощью всего одной сборки, размер бинарного файла составляет менее 700 КБ, и его также очень легко встраивать в различные нативные проекты.
  • Личная сила автора. Автор Фабрис Беллар для меня богоподобен. Подобно QEMU в основе эмулятора Android и FFmpeg, который необходим разработчикам аудио и видео, он создавал шедевры. Всякий раз, когда я получаю какой-то технический прогресс, посещаю егоHome PageЭто всегда заставляет меня осознавать, насколько я мал.

Однако QuickJS — это всего лишь новый проект, выпущенный всего несколько месяцев назад, и не так много людей отваживаются его попробовать. Даже если он пройдет различные модульные тесты, сможет ли он действительно стабильно запускать JS-проект промышленного уровня, такой как React? Это ключевой вопрос, который определяет осуществимость этого технического маршрута.

Для этого, конечно, нам нужно сначала использовать QuickJS. Его исходный код является кроссплатформенным не только для Linux или Raspberry Pi. В моей macOS вытащите набор кодов из трех качеств:

cd quickjs
make
sudo make install

Таким образом, мы можем ввести в терминалqjsкоманда для входа в интерпретатор QuickJS. покаqjs foo.jsform, вы можете использовать его для выполнения вашего скрипта. Плюс-mпараметр, он может поддерживать загрузку модулей в виде модуля ES (ESM) и напрямую запускать весь модульный проект JS.

Обратите внимание, что при использовании ESM в QuickJS необходимо добавить полный путь к пути.jsсуффикс. Это согласуется с требованием загружать ESM непосредственно в браузере.

Однако QuickJS не может напрямую запускать «тот React, который мы пишем каждый день». В конце концов, JSX с вкладками — это просто диалект, а не отраслевой стандарт. Как это сделать? В качестве обходного пути я ввел вспомогательную среду Node.js, сначала упаковал и перевел JSX-код в формат ESM с помощью Rollup, а затем передал его QuickJS для выполнения. Объем node_modules этой вспомогательной среды составляет менее 10 МБ, и конкретная конфигурация не будет повторяться.

Скоро наступит решающий шаг, ты думаешьqjs react.jsЭто действительно работает? В это время отражается дизайнерское превосходство React — еще два года назад, когда был выпущен React 16.0, React отделил верхний слой в архитектуре.reactи базовый рендерер DOM по умолчаниюreact-dom, они проходят черезreact-reconcilerИнкапсулированный средний слой волокна для соединения.reactПакет не зависит от DOM и может работать независимо в чистой среде JS. Хотя этот инженерный проект увеличивает общий размер проекта, он очень полезен для таких случаев, как мы, когда нам нужно настроить серверную часть рендеринга, а также это место, где React опережает Vue более чем на два года. Как я могу убедиться, что React доступен? Просто попробуйте написать простейший компонент без состояния:

import './polyfill.js'
import React from 'react'

const App = props => {
  console.log(props.hello)
  return null
}

console.log(<App hello={'QuickJS'} />)

уведомлениеpolyfill.jsВсе же? Это совместимый код, необходимый для переноса React в среду QuickJS. Может показаться, что такая работа с совместимостью сложна, но на самом деле это довольно просто, вот так:

// QuickJS 约定的全局变量为 globalThis
globalThis.process = { env: { NODE_EMV: 'development' } }
globalThis.console.warn = console.log

После того, как такой код будет упакован Rollup, выполнитеqjs dist.jsВы можете получить этот результат:

$ qjs ./dist.js
QuickJS
null

это означаетReact.createElementЕго можно правильно выполнить, и нет проблем с передачей Props. Этот результат волнует меня, потому что даже остановка на этом шаге уже говорит:

  • QuickJS может напрямую запускать отраслевые фреймворки, прошедшие боевые испытания.
  • npm install reactисходный код, способныйнеизменная строкаРаботает на совместимом со стандартами движке JS.

Что ж, QuickJS великолепен! Реакция классная! Что дальше?

Дисковое оборудование на основе языка C

У нас есть React, который гладко работает на движке QuickJS. Но не забывайте о нашей цели — рендерить React напрямую вЖК-экран! Как отображать контент на ЖК-экране? Ближайший к аппаратному обеспечению язык Си, безусловно, самый удобный. Но прежде чем мы начнем программировать, нам нужно понять эти концепции:

  • Самый простой способ управления чипом SSD1306 — через протокол связи I2C. По той же причине диск U поддерживает протокол USB.
  • На обычной материнской плате ПК нет интерфейса I2C, но на Raspberry Pi вам нужно всего лишь подключить несколько контактов.
  • После подключения устройства с поддержкой I2C им можно управлять из операционной системы. Мы знаем, что в Linux все является файлом, поэтому этот экран также будет рассматриваться как файл и монтироваться в/devПод содержанием.
  • Для файлов просто напишите Unix на Copen / writeВ ожидании системного вызова можно читать и записывать управление. Однако дисплей I2C — это не обычный файл, он управляется драйвером в ядре Linux. Для этого нам нужно установитьlibi2c-devЭтот пакет используется в пользовательском режиме для передачиioctlсистемные вызовы для управления им.

Сначала нам нужно подключить чип экрана к Raspberry Pi. Метод заключается в следующем (номер контакта Raspberry Pi можно использовать сpinoutкоманда для просмотра):

  • Чип Vcc подключен к контакту 1 Raspberry Pi, который является источником питания 3,3 В.
  • Чип Gnd подключается к контакту 14 Raspberry Pi, который является заземляющим проводом.
  • Чип SCL подключен к контакту 5 Raspberry Pi, который является портом SCL спецификации I2C.
  • Чип SDA подключен к контакту 3 Raspberry Pi, который является портом SDA спецификации I2C.

После подключения должно получиться так:

Затем в «Конфигурации системы» «Пускового меню» Raspberry Pi включите пункт I2C в интерфейсе (этот шаг также можно выполнить, набрав команды) и перезапустите, чтобы включить поддержку I2C.

После настройки оборудования и системы давайте установим некоторые инструменты для I2C:

sudo apt-get install i2c-tools libi2c-dev

Как проверить, что описанный выше процесс в порядке? использоватьi2cdetectкоманда сделает. Если вы видите следующее3cРезультат со значением в позиции указывает на то, что экран установлен правильно:

$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

После того, как конфигурация среды завершена, мы можем написать код C, который использует системные вызовы, такие как open/write/ioctl, для управления экраном. Это требует некоторых знаний о протоколе связи I2C, но, к счастью, есть довольно много готовых колес. используется здесьoled96Библиотека, основанная на ее примере кода, выглядит так:

// demo.c
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "oled96.h"

int main(int argc, char *argv[])
{
    // 初始化
    int iChannel = 1, bFlip = 0, bInvert = 0;
    int iOLEDAddr = 0x3c;
    int iOLEDType = OLED_128x64;
    oledInit(iChannel, iOLEDAddr, iOLEDType, bFlip, bInvert);

    // 清屏后渲染文字和像素
    oledFill(0);
    oledWriteString(0, 0, "Hello OLED!", FONT_SMALL);
    oledSetPixel(42, 42, 1);

    // 在用户输入后关闭屏幕
    printf("Press ENTER to quit!\n");
    getchar();
    oledShutdown();
}

Этот пример требует толькоgcc demo.cкоманда будет работать. Если ничего другого, запуск компиляции производит./a.outчтобы зажечь экран. Код, написанный на этом шаге, также очень прост для понимания.Настоящая сложность заключается в реализации связи на уровне драйвера oled96. Заинтересованные студенты могут прочитать его исходный код.

Обертывание расширений языка C для движков JS

Теперь React World и Hardware World будут работать нормально. Но как их соединить? Нам нужно разработать модули языка C для движка QuickJS.

По умолчанию встроен в QuickJS.osа такжеstdДва нативных модуля, такие как тот код, который мы привыкли видеть:

const hello = 'Hello'
console.log(`${hello} World!`)

На самом деле, в QuickJS это можно написать и так:

import * as std from 'std'

const hello = 'Hello'
std.out.printf('%s World!', hello)

Есть ли ощущение обстрела из языка Си? здесьstdМодуль на самом деле автор языка Cstdlib.hа такжеstdio.hРеализован JS Binding. Итак, что мне делать, если я хочу реализовать другие модули C самостоятельно? Официальный документ махнул рукой и сказал вам: «Просто следуйте моему исходному коду, чтобы написать его» — осмелитесь использовать исходный код ядра в качестве примера для Xiaobai, может быть, это великий бог.

После некоторого метания я обнаружил, что дизайн QuickJS при доступе к нативным модулям очень «художественный и смелый». Первое, что нам нужно знать, это то, что вqjsКроме того, QuickJS также предоставляетqjscкоманда, может написать Hello Worldhello.jsСкомпилируйте непосредственно в двоичный исполняемый файл или код C следующим образом:

/* File generated automatically by the QuickJS compiler. */
#include "quickjs-libc.h"
const uint32_t qjsc_hello_size = 87;
const uint8_t qjsc_hello[87] = {
 0x01, 0x04, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f,
 0x6c, 0x65, 0x06, 0x6c, 0x6f, 0x67, 0x16, 0x48,
 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72,
 0x6c, 0x64, 0x22, 0x65, 0x78, 0x61, 0x6d, 0x70,
 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c,
 0x6f, 0x2e, 0x6a, 0x73, 0x0d, 0x00, 0x06, 0x00,
 0x9e, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00,
 0x14, 0x01, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x39,
 0xd0, 0x00, 0x00, 0x00, 0x43, 0xd1, 0x00, 0x00,
 0x00, 0x04, 0xd2, 0x00, 0x00, 0x00, 0x24, 0x01,
 0x00, 0xcc, 0x28, 0xa6, 0x03, 0x01, 0x00,
};

int main(int argc, char **argv)
{
  JSRuntime *rt;
  JSContext *ctx;
  rt = JS_NewRuntime();
  ctx = JS_NewContextRaw(rt);
  JS_AddIntrinsicBaseObjects(ctx);
  js_std_add_helpers(ctx, argc, argv);
  js_std_eval_binary(ctx, qjsc_hello, qjsc_hello_size, 0);
  js_std_loop(ctx);
  JS_FreeContext(ctx);
  JS_FreeRuntime(rt);
  return 0;
}

Куда делся ваш Hello World? в этом большом массивебайт-кодкуда. Вот некоторые, какJS_NewRuntimeМетод C на самом деле является частью внешнего API QuickJS. Вы можете обратиться к этому методу для доступа к QuickJS в нативном проекте — настоящий бог, даже если вы скомпилируете свой собственный код, это все еще код учебника уровня примера.

После понимания этого процесса нетрудно обнаружить, что самый простой способ использования нативных модулей в QuickJS на самом деле выглядит так:

  1. использоватьqjscСкомпилируйте весь код JS на языке Cmain.cВход
  2. Возьмите каждый исходный код C по очереди, используяgcc -cКоманда компилируется в.oформатированный объектный файл
  3. компилироватьmain.cи связать эти.oфайл, получить окончательныйmainзапускаемый файл

Вы понимаете? Ядром этой операции являетсяСначала скомпилируйте JS в обычный C, а затем линкуйте различные нативные модули в мире C.. Хотя некоторые фантазии, но преимущество в том, что не нужно магии для изменения источника QuickJS может быть достигнуто. Таким образом, у меня была реализация на основе oled96 с именемrenderer.c, который предоставляет модуль C с именемrendererНативные модули JS. Общая реализация примерно такова:

// 用于初始化 OLED 的 C 函数
JSValue nativeInit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
    const int bInvert = JS_ToBool(ctx, argv[0]);
    const int bFlip = JS_ToBool(ctx, argv[1]);
    int iChannel = 1;
    int iOLEDAddr = 0x3c;
    int iOLEDType = OLED_128x64;
    oledInit(iChannel, iOLEDAddr, iOLEDType, bFlip, bInvert);
    oledFill(0);
    return JS_NULL;
}

// 用于绘制像素的 C 函数
JSValue nativeDrawPixel(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
    int x, y;
    JS_ToInt32(ctx, &x, argv[0]);
    JS_ToInt32(ctx, &y, argv[1]);
    oledSetPixel(x, y, 1);
    return JS_NULL;
}

// 定义 JS 侧所需的函数名与参数长度信息
const JSCFunctionListEntry nativeFuncs[] = {
    JS_CFUNC_DEF("init", 2, nativeInit),
    JS_CFUNC_DEF("drawPixel", 2, nativeDrawPixel)};

// 其他的一些胶水代码
// ...

Весь этап компиляции проекта, включая модули C, усложняется, если выполняется вручную. Поэтому мы решили представить GNU Make, чтобы выразить весь процесс сборки. Так как это первый раз, когда я пишу Makefile, этот процесс меня немного сбивает с толку. Но после понимания принципа это на самом деле не так уж и страшно. Заинтересованные студенты могут самостоятельно проверить реализацию в адресе склада с открытым исходным кодом.

Пока приведенный выше модуль C успешно скомпилирован, мы можем напрямую управлять этим экраном с помощью кода JS, который могут легко получить студенты внешнего интерфейса:

// main.js
import { setTimeout } from 'os'
import { init, clear, drawText } from 'renderer'

const wait = timeout =>
  new Promise(resolve => setTimeout(resolve, timeout))

;(async () => {
  const invert = false
  const flip = false
  init(invert, flip)
  clear()
  drawText('Hello world!')
  await wait(2000)

  clear()
  drawText('Again!')
  await wait(2000)

  clear()
})()

Фактически, многие известные модули Python для Raspberry Pi также делают это за вас. Так зачем повторно реализовывать это в JS? Потому что только у JS есть Учись один раз, пиши где угодно Реагируй! Давайте сделаем последний шаг и подключим React к этому ЖК-экрану.

Реализация бэкенда рендеринга React

Внедрение бэкэнда рендеринга для React звучит как довольно большое дело. На самом деле это дело наверняка не такое сложное, как вы думаете, и у сообщества тоже естьMaking a custom React rendererТакой хороший учебник, чтобы показать вам, как реализовать свой собственный рендерер с нуля. Но для меня одного этого урока недостаточно. Ключ лежит в двух местах:

  1. Это руководство преобразует React только в статический формат docx и не поддерживает интерфейсы пользовательского интерфейса, которые можно постоянно обновлять.
  2. В этом руководстве не рассматривается доступ к нативным модулям в стиле React Native.

Из этих двух проблем проблема 2 в основном решена выше: у нас уже есть нативный модуль, который может что-то нарисовать с помощью JS один раз. Итак, остается вопрос, как реализовать серверную часть рендеринга React, которая поддерживает обновления по запросу?

Базовый дизайн, который я выбрал, состоит в том, чтобы разделить все приложение на три макророли:

  • Архитектура React, управляемая событиями
  • Контейнер, который поддерживает собственное состояние экрана
  • Рендеринг основного цикла с фиксированной частотой кадров

Как эти системы работают вместе? Проще говоря, когда пользовательское событие запускает setState в React, React не только обновляет собственное дерево состояний, но также вносит изменения и маркеры в собственный контейнер состояний. Таким образом, когда приходит следующий кадр основного цикла, мы можем обновить состояние экрана по мере необходимости на основе маркера.С точки зрения хода событий, общая архитектура выглядит так:

Контейнер Native State Container на рисунке можно понимать как контейнер состояния, который «нетрудно напрямую написать на JS для управления, но лучше оставить это React, чтобы помочь вам управлять» реальным DOM браузера. Пока конфигурация правильная, React будет однонаправленно обновлять состояние контейнера. И как только состояние контейнера обновляется, это новое состояние синхронизируется с экраном в следующем кадре. На самом деле это очень похоже на классическую модель производитель-потребитель. React — это производитель, который обновляет состояние контейнера, а экран — это потребитель, который периодически проверяет и потребляет состояние контейнера. Кажется, это не должно быть сложно, верно?

Реализация контейнеров с собственным состоянием и основного цикла на самом деле очень проста. Большой вопрос заключается в том, как настроить React для автоматического обновления этого контейнера состояния? Для этого необходимо использовать знаменитый React Reconciler. Чтобы реализовать React Renderer, вам нужно только правильно обновить контейнер собственного состояния в каждом хуке жизненного цикла Reconciler.С иерархической точки зрения, общая структура выглядит следующим образом:

Можно считать, что JS Renderer, который мы хотим использовать в React, больше похож на тонкую оболочку. Под ним есть две важные структуры, которые нам нужно реализовать:

  • Уровень адаптации адаптера, который реализует контейнеры собственного состояния и собственные циклы рендеринга.
  • Настоящий визуализатор языка C

Реализация оболочки Renderer, используемая React, примерно такова:

import Reconciler from 'react-reconciler'
import { NativeContainer } from './native-adapter.js'

const root = new NativeContainer()
const hostConfig = { /* ... */ }
const reconciler = Reconciler(hostConfig)
const container = reconciler.createContainer(root, false)

export const SSD1306Renderer = {
  render (reactElement) {
    return reconciler.updateContainer(reactElement, container)
  }
}

Среди них нам нужно реализовать контейнер NativeContainer. Контейнер выглядит так:

// 导入 QuickJS 原生模块
import { init, clear, drawText, drawPixel } from 'renderer'
// ...

export class NativeContainer {
  constructor () {
    this.elements = []
    this.synced = true
    // 清屏,并开始事件循环
    init()
    clear()
    mainLoop(() => this.onFrameTick())
  }
  // 交给 React 调用的方法
  appendElement (element) {
    this.synced = false
    this.elements.push(element)
  }
  // 交给 React 调用的方法
  removeElement (element) {
    this.synced = false
    const i = this.elements.indexOf(element)
    if (i !== -1) this.elements.splice(i, 1)
  }
  // 每帧执行,但仅当状态更改时重新 render
  onFrameTick () {
    if (!this.synced) this.render()
    this.synced = true
  }
  // 清屏后绘制各类元素
  render () {
    clear()
    for (let i = 0; i < this.elements.length; i++) {
      const element = this.elements[i]
      if (element instanceof NativeTextElement) {
        const { children, row, col } = element.props
        drawText(children[0], row, col)
      } else if (element instanceof NativePixelElement) {
        drawPixel(element.props.x, element.props.y)
      }
    }
  }
}

Нетрудно заметить, что этот NativeContainer будет вызывать модуль рендеринга C в следующем кадре при каждом изменении внутренних элементов. Так как же заставить React вызывать его методы? Для этого требуется вышеhostConfigнастроен. Эта конфигурация требует реализации большого количества Reconciler API. Для нашей простейшей сцены первого рендеринга они включают в себя:

appendInitialChild () {}
appendChildToContainer  () {} // 关键
appendChild () {}
createInstance () {} // 关键
createTextInstance () {}
finalizeInitialChildren () {}
getPublicInstance () {}
now () {}
prepareForCommit () {}
prepareUpdate () {}
resetAfterCommit () {}
resetTextContent () {}
getRootHostContext () {} // 关键
getChildHostContext () {}
shouldSetTextContent () {}
useSyncScheduling: true
supportsMutation: true

Реальные значимые реализации здесь в основном находятся в пунктах, помеченных как «критические». Например, предположим, что в моем контейнере NativeContainer есть элементы NativeText и NativePixel, тогдаcreateInstanceВ хуке должен быть создан соответствующий экземпляр элемента в соответствии с типом компонента React, а соответствующий экземпляр элемента должен быть создан в хуке.appendChildToContainerловушки для добавления этих экземпляров в NativeContainer. Конкретная реализация довольно проста, вы можете обратиться к фактическому коду.

После создания у нас также есть возможность обновлять и удалять элементы. Это соответствует как минимум этим API-интерфейсам Reconciler:

commitTextUpdate () {}
commitUpdate () {} // 关键
removeChildFromContainer () {} // 关键

Их реализация одинакова. Наконец, нам нужно упаковать некоторые «встроенные компоненты» с визуализатором, например:

export const Text = 'TEXT'
export const Pixel = 'PIXEL'
// ...
export const SSD1306Renderer = {
  render () { /* ... */ }
}

Таким образом, тип компонента, который мы получаем от Reconciler, может быть этими константами, а затем сообщать NativeContainer об обновлении.

Пока что, после всего этого процесса, мы наконец-то можем управлять экраном напрямую с помощью React.! Как только этот рендерер реализован, код, основанный на нем, становится довольно простым:

import './polyfill.js'
import React from 'react'
import { SSD1306Renderer, Text, Pixel } from './renderer.js'

class App extends React.Component {
  constructor () {
    super()
    this.state = { hello: 'Hello React!', p: 0 }
  }

  render () {
    const { hello, p } = this.state
    return (
      <React.Fragment>
        <Text row={0} col={0}>{hello}</Text>
        <Text row={1} col={0}>Hello QuickJS!</Text>
        <Pixel x={p} y={p} />
      </React.Fragment>
    )
  }

  componentDidMount () {
    // XXX: 模拟事件驱动更新
    setTimeout(() => this.setState({ hello: 'Hello Pi!', p: 42 }), 2000)
    setTimeout(() => this.setState({ hello: '', p: -1 }), 4000)
  }
}

SSD1306Renderer.render(<App />)

Результат рендеринга выглядит так:

Не смотрите на эффект отображения, кажется, что внешний вид не удивителен, внешний вид этих строк текста, стандартный JSX, хуки жизненного цикла компонентов и потенциальные хуки / Redux и другие современные технологии фронтенда, наконец, могут быть напрямую связаны к встроенному оборудованию - React, Попытка подключить QuickJS, Raspberry Pi и ЖК-экран подошла к концу. Благодаря QuickJS,В конце концов, размер всего бинарного исполняемого файла, включая движок JS и корзину семейства React, составляет всего около 780 КБ..

ресурс

Все примеры кода проекта, приведенные выше, общедоступны.react-ssd1306В репозитории (поставьте звездочку, если вам это интересно). Вот несколько полезных справочных ссылок в процессе:

постскриптум

Если вы зашли так далеко, это действительно сложно для вас ~ Эта статья довольно длинная, и ключевые моменты могут быть разрознены — основное внимание уделяется тому, как использовать QuickJS, как писать расширения C или как настроить React Reconciler. ? Кажется, это очень важно (смеется). Тем не менее, процесс подбрасывания действительно дал мне много преимуществ. Есть много концепций, о которых я раньше только слышал, или которые я считаю концепциями очень высокого уровня.После того, как я сделал это сам, я понимаю, что это не так уж и далеко. Некоторые другие мысли могут включать:

  • С таким удобным встроенным движком JS стек веб-технологий может лучше выходить из браузера
  • Raspberry Pi — это действительно весело и еще эффективнее с VSCode Remote. Настоятельно рекомендуется начать играть
  • Узкое место производительности I2C действительно очевидно, и оптимизации всей системы определенно недостаточно на стороне React.
  • Каковы «минимальные требования» для запуска React? Должна быть намного ниже минимальной конфигурации Node.js.

На самом деле, с тех пор, как я каждый день вырезаю картинки, мне нравится получать вещи, которые «не имеют непосредственной ценности для бизнеса», например:

На этот раз в проекте react-ssd1306 движущая сила, которая движет мной, похожа на создание этих колес. Почему бы не написать хорошо бизнес-логику и не заняться этими «бессмысленными» вещами?

Because we can.

Я в первую очередь фронтенд-разработчик. Если вы заинтересованы в редактировании структурированных веб-данных, рендеринге WebGL, разработке гибридных приложений или мыслях компьютерных энтузиастов, подписывайтесь на меня или на мою официальную учетную запись.color-albumОй :)