Объектно-ориентированное управление состоянием с помощью WebAssembly и Rust

Управление состоянием в любом приложении - это всегда супер задача, которую нужно решить. Когда дело доходит до интеграции WebAssembly с существующими приложениями или создания нового проекта с нуля, эта проблема становится еще более интересной, но это не так сложно, как может показаться.

Прежде чем мы перейдем к делу, я хочу сообщить, что это, возможно, еще не самое эффективное решение для общего управления состоянием (здесь важно пока). Взаимодействие между JavaScript и WebAssembly по-прежнему страдает некоторыми ограничениями производительности, когда дело доходит до сериализации и десериализации сложных структур данных, но оно улучшается, и есть предложения, которые находятся в процессе разработки, которые могут оказать значительное положительное влияние на представление.

Прыгать в

В этом примере мы собираемся создать супер-базовое приложение счетчика - вы сможете увеличивать и уменьшать счетчик с помощью кнопок + и -. Это будет охватывать очень поверхностную информацию и базовую реализацию, но не будет углубляться в паттерны управления состоянием, такие как поток с Rust или как создать форму регистрации; это сообщения для другого раза, и я планирую осветить эти темы в ближайшее время, если люди сочтут это пошаговое руководство полезным.

Высокий уровень

Используя приведенную выше диаграмму, мы можем рассматривать наше приложение как три отдельные части:

  1. Представление - наш HTML-документ, с которым будет взаимодействовать пользователь.
  2. Ссылка - наш код JavaScript, который устраняет разрыв между нашим представлением и нашим слоем состояния.
  3. Состояние - наш код 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 />.

Если вы чувствуете, что просто покажите мне код!, вы можете просмотреть его здесь.