Объектно-ориентированное управление состоянием с помощью WebAssembly и Rust
Управление состоянием в любом приложении - это всегда супер задача, которую нужно решить. Когда дело доходит до интеграции WebAssembly с существующими приложениями или создания нового проекта с нуля, эта проблема становится еще более интересной, но это не так сложно, как может показаться.
Прежде чем мы перейдем к делу, я хочу сообщить, что это, возможно, еще не самое эффективное решение для общего управления состоянием (здесь важно пока). Взаимодействие между JavaScript и WebAssembly по-прежнему страдает некоторыми ограничениями производительности, когда дело доходит до сериализации и десериализации сложных структур данных, но оно улучшается, и есть предложения, которые находятся в процессе разработки, которые могут оказать значительное положительное влияние на представление.
Прыгать в
В этом примере мы собираемся создать супер-базовое приложение счетчика - вы сможете увеличивать и уменьшать счетчик с помощью кнопок + и -. Это будет охватывать очень поверхностную информацию и базовую реализацию, но не будет углубляться в паттерны управления состоянием, такие как поток с Rust или как создать форму регистрации; это сообщения для другого раза, и я планирую осветить эти темы в ближайшее время, если люди сочтут это пошаговое руководство полезным.
Высокий уровень
Используя приведенную выше диаграмму, мы можем рассматривать наше приложение как три отдельные части:
- Представление - наш HTML-документ, с которым будет взаимодействовать пользователь.
- Ссылка - наш код JavaScript, который устраняет разрыв между нашим представлением и нашим слоем состояния.
- Состояние - наш код Rust, который заботится о состоянии приложения и предоставляет интерфейс для нашего JavaScript для чтения и записи.
Слой View относительно прост - пара кнопок и <div />
для отображения состояния нашего счетчика. JavaScript, необходимый для подключения нашего представления к состоянию нашего приложения, в основном генерируется библиотекой Rust с именем wasm_bindgen
, но нам все равно нужно будет использовать сгенерированные привязки в нашем пользовательском JavaScript.
Уровень состояния является наиболее интересным и сложным из трех, но при правильной реализации он может фактически обеспечить очень чистый интерфейс, через который мы взаимодействуем с состоянием нашего приложения.
Начальная настройка
Сначала мы собираемся создать базовый проект на JavaScript, WebAssembly и Rust. Убедитесь, что вы rust
установили с помощью rustup
- инструкции здесь. Также необходимо убедиться, что wasm-pack
установлен - ссылка здесь.
Мы сгенерируем проект - если у вас возникнут трудности с этим шагом, возможно, вам придется использовать sudo
.
npm init rust-webpack counter-app
Затем мы собираемся построить и запустить проект - опять же, возможно, потребуется использовать sudo
.
npm run build && npm run start
Вы должны увидеть пустую страницу localhost:8080
с Hello world!
, вошедшим в консоль. Если вы посмотрите файл src/lib.rs
, то в проекте по умолчанию для создания этого сообщения используются ящики web_sys
и wasm_bindgen
(библиотеки Rust).
Код
Итак, теперь, когда у нас есть проект, нам нужно написать код. Если вы еще не знакомы с Rust, я настоятельно рекомендую прочитать Книгу.
Для начала мы воспользуемся некоторыми парадигмами объектно-ориентированного программирования. ООП в Rust - интересное животное, и это не обязательно самый идиоматический подход, но может быть более легким переходом для людей, пришедших из опыта ООП. О более функциональном стиле мы поговорим в отдельном посте.
В нашем объектно-ориентированном подходе мы собираемся использовать только Rust для управления состоянием и не будем использовать web_sys
для генерации нашего HTML через привязки JavaScript.
Ржавчина
Сначала давайте создадим новый файл с именем counter_state.rs
в нашем src
каталоге:
Здесь что-то происходит -
Сначала мы создаем общедоступный Rust struct
, затем реализуем его struct
с помощью ключевого слова impl
.
примечание: все struct
с привязками JavaScript, сгенерированные wasm_bindgen
, должны использовать ключевое слово pub
.
Ключевым показателем того, что мы используем Rust в стиле ООП, является то, что в нашей struct
реализации мы добавляем общедоступный new()
метод, который будет возвращать экземпляр ранее определенного CounterState
struct
.
В дополнение к методу new()
мы также предоставили три других общедоступных метода: increment_counter()
, decrement_counter()
и get_counter()
. Свойство counter
на CounterState
struct
является частным и не предоставляется потребителю.
Важно: нам также нужно будет добавить этот counter_state
модуль к нашему импорту в src/lib.rs
файле. Добавьте строку: mod counter_state;
в начало файла под другими импортированными файлами.
HTML
Следующим шагом будет обновление нашего static/index.html
файла, включив в него элементы <button />
, а также элемент, в котором мы будем отображать состояние счетчика:
JavaScript
Прежде чем мы сможем создать клей для JavaScript для соединения HTML документа с состоянием Rust, нам сначала нужно обновить наш package.json
файл, чтобы обеспечить доступ к нашему модулю WebAssembly, добавив "wasm": "file:pkg"
в наш dependencies
- вам тоже нужно будет снова запустить npm i
.
Наконец, мы можем добавить JavaScript, который будет обращаться к нашему модулю WebAssembly с отслеживанием состояния. Он войдет в js/counter.js
файл:
Нам также нужно будет обновить наш js/index.js
файл, чтобы импортировать counter.js
файл вместо файла pkg/index.js
:
В counter.js
файл мы импортируем CounterState
класс JavaScript, который wasm_bindgen
сгенерировал как привязку для нашего Rust struct
. Сгенерированный код выглядит так:
Поскольку теперь у нас есть доступ к этому class
, у нас также есть доступ к общедоступным методам в Rust struct
- то, что мы делаем в строке 3 файла counter.js
, - это создание экземпляра struct
в WebAssembly и присвоение его переменной JavaScript используя метод .new()
, который мы создали в нашем counter_state.rs
файле.
Отсюда мы устанавливаем исходное текстовое содержимое #counter
HTML-элемента с помощью метода get_counter()
. Мы также добавляем прослушиватели событий к элементам <button />
в нашем HTML-документе, которые будут увеличивать и уменьшать состояние нашего счетчика.
Оба метода increment_counter()
и decrement_counter()
возвращают состояние после модификации частного свойства counter
, поэтому нам не нужно использовать get_counter()
второй раз.
Чтобы убедиться, что мы успешно внедрили счетчик, мы запускаем:
npm i && npm run build && npm run start
Оформите заказ localhost:8080
, и вы должны увидеть что-то вроде этого:
Заключение
Объектно-ориентированное управление состоянием с помощью Rust и WebAssembly не только очень возможно, но и относительно просто рассуждать. Подобно другим решениям для управления состоянием, вы по-прежнему создаете какое-то постоянное хранилище и делаете его доступным в качестве модуля для вашего средства визуализации, но с модулями WebAssembly вы можете повысить производительность для методов с интенсивными вычислениями, добавить безопасность типов и другие особенности, которые делают Rust отличным.
Этот пример охватывает только проблемные области на уровне поверхности. Мы не используем сложные типы или нам не нужно управлять сериализацией или десериализацией - это немного усложняет задачу, но в ближайшие недели я напишу еще один пост, посвященный всему этому в контексте создания <form />
.
Если вы чувствуете, что просто покажите мне код!, вы можете просмотреть его здесь.