Как написать фронтенд на Rust в 2020 году

внешний интерфейс

Язык Rust — интересный язык, после изучения Rust я хотел найти что-то для практики, и тогда я нашел фреймворк Yew, написанный на Rust, который может писать веб-страницы на Rust. Из-за того, что я не знаком с цепочками инструментов, связанными с Rust, я чувствую, что вернулся, когда впервые столкнулся с React + Webpack, и я был сбит с толку и не имел никаких подсказок. В то время я написал приложение Todo, чтобы помочь мне ознакомиться с цепочкой инструментов, и сейчас, конечно, я продолжаю повторять то, что я делал в качестве новичка, пишу приложение Todo, чтобы ознакомиться с цепочкой инструментов!

представлять

Что такое Ю?

Yew— это продвинутая среда Rust, предназначенная для создания многопоточных интерфейсных веб-приложений с использованием WebAssembly.

  • фреймворк на основе компонентов, вы можете легко создать интерактивный пользовательский интерфейс. Разработчики, имеющие опыт работы с такими фреймворками, как React или Elm, будут чувствовать себя комфортно, используя Yew.
  • высокая производительность, внешние разработчики могут легко переложить работу на серверную часть, чтобы уменьшить количество вызовов DOM API и добиться исключительной производительности.
  • Поддержка взаимодействия с JavaScript, что позволяет разработчикам использовать пакеты NPM и интегрироваться с существующими приложениями JavaScript.

Внешний вид приложения

Давайте сначала посмотрим на внешний вид приложения, чтобы сосредоточиться наНаписание интерфейсов на RustДля этой цели я непосредственно переиспользовал«Я создал одно и то же приложение в React и Vue. Вот различия»Код стиля

Структура каталогов

├── Cargo.lock
├── Cargo.toml
├── README.md
├── docs // 编译后后的文件
|  ├── README.md
|  ├── assets
|  |  ├── rust.svg
|  |  ├── style // 应用 css
|  |  └── yew.svg
|  ├── index.html
|  ├── package.json // 编译产物
|  ├── wasm.d.ts // 编译产物
|  ├── wasm.js // 编译产物
|  ├── wasm_bg.wasm // 编译产物
|  └── wasm_bg.wasm.d.ts // 编译产物
├── src // 应用代码
|  ├── app.rs // 应用入口
|  ├── components // 组件
|  |  ├── mod.rs
|  |  ├── todo
|  |  |  └── mod.rs // Todo 组件
|  |  └── todo_item
|  |     └── mod.rs // TodoItem 组件
|  ├── lib.rs
|  ├── model.rs // 类型存放处
|  └── utils.rs // 一些工具函数
└── tests
   └── web.rs

Тисовый компонент

一个Yew组件

Вот как выглядит компонент Yew По аналогии мы опишем, как основные понятия современных интерфейсных фреймворков соответствуют Yew.

Где хранится состояние?

pub struct Todo {
  link: ComponentLink<Self>,
  list: Vec<model::TodoItem>,
  input: String,
  show_error: bool,
}

impl Component for TodoItem {   
// ... codes
}

Грубо говоря, это можно представить как объявление класса TodoItem со свойствами link, list, input, show_error, но это еще не компонент Yew! потому что это не... extends React.Component", необходимо добавить следующееimpl Component for TodoItemкомпонент

Если вы хотите сравнить его с компонентами React, вот он

class Todo extends React.Component {
	constructor(props) {
		super(props)
		this.state = {
			list: [],
			input: '',
			show_error: false,
		};
	}
}

Состояние компонента Yew хранится в нем самом, или примерно напрямую монтируется на this вместо this.state .

  • .linkсвойство хранитсяComponentLink, это мостик между компонентом и Тисом, он нам нужен для запуска повторного рендера компонента, его роль такая же как у Реактаthis.setState()очень похожий
  • .listХранит данные списка задач, который нам нужно отобразить, и его тип может быть аппроксимирован как Array в JavaScript.
  • .inputхранить наши<input />ввод в поле
  • .show_errorИспользуется для управления отображением сообщений об ошибках

Как принять данные от родительского компонента

#[derive(Properties, Clone)]
pub struct TodoItemProps {
    pub item: model::TodoItem,
    pub delete: Callback<i32>,
}

pub struct TodoItem {
    link: ComponentLink<Self>,
    props: TodoItemProps,
}

impl Component for TodoItem {   
    // ... codes
}

Как и React, Yew — это односторонний поток данных. Объявив новый тип Props Struct и назначив его компоненту, вы можете.propsПолучите доступ к данным, переданным родительским компонентом. Оператор Props приведенного выше кода означает, чтоTodoItemЭтот компонент принимаетTodoItemтип данных и функция обратного вызова.

А так как RustСтрогая типизацияязыка, поэтому во время компиляции проверяется, соответствует ли тип передаваемых данныханастомоз, если не совпадает, то не пройдет компиляцию, и ошибка будет обнаружена заранее.

функция рендеринга

impl Component for TodoItem {   
		// ... codes
    fn view(&self) -> Html {
        // render 函数
        html! {
            <div class="ToDoItem">
                <p class="ToDoItem-Text">{&self.props.item.text}</p>
                <button 
                    onclick={self.link.callback(|_| Msg::OnClick)}
                    class="ToDoItem-Delete"
                >
                { "-" } 
                </button>
            </div>
        }
    }
		// ... codes
}

соответствует реакцииrender()концепцияview()метод, здесь больше всего стоит упомянуть, что приведенный выше код, который вы видите,Полностью соответствует правилам синтаксиса Rust.из! Rust имеет мощный механизм макросов, который может динамически генерировать код во время компиляции. Используя макросы, DSL можно легко реализовать в Rust без необходимости использования транспиляторов, таких как babel.

Как обновить состояние компонента

pub enum Msg {
  UpdateInput(String),
  AddTodoItem,
  DeleteItem(i32),
  None,
}

Msgэто тип перечисления, который играет ту же роль, что и ReduxActionОчень похоже, примерно можно подумать, что приведенный выше код эквивалентен следующему коду

const updateInputAction = {
	type: 'UpdateInput',
	payload: str,
};
const addTodoItem = {
	type: 'AddTodoItem',
};
const deleteItem = {
	type: 'DeleteItem',
	payload: id,
};

приниматьMsgдаupdate()метод, аналогичныйshouldComponentUpdate()а такжеreducer()комбинация, мы находимся вupdate()побочные эффекты

impl Component for Todo {
  type Message = Msg;
	// ... codes
  fn update(&mut self, msg: Self::Message) -> ShouldRender {
    match msg {
      Msg::UpdateInput(input) => {
        self.input = input;
        true
      },
      Msg::AddTodoItem => {
        if self.input.trim().len() == 0 {
          self.show_error = true;
        } else {
          self.list.push(model::TodoItem {
            id: self.list.len() as i32,
            text: self.input.clone(),
          });
        }
        self.input = String::new();
        true
      },
      Msg::DeleteItem(id) => {
        self.list = self.list.clone().into_iter().filter(|item| item.id != id).collect();
        true
      }
      _ => true
    }
  }
	// ... codes  
}

мы в<button onclick=self.link.callback(|_| Msg::AddTodoItem) />Функция, привязанная к обработчику события onclick, при нажатии кнопки возвращаемое значение функции обработчика будетMsg::AddTodoItemотправляется вupdate()мы изменяем его собственное состояние или вызываем функцию обратного вызова, переданную родительским компонентом, в соответствии с типом входящего сообщения и возвращаем логическое значение, чтобы сообщить Yew, нужно ли его повторно отображать.

Если он возвращает true , Yew повторно выполнитview()функции, потому что мы изменили свое собственное состояние, поэтому в настоящее времяview()Соответствующее виртуальное дерево DOM будет возвращено в соответствии с новым состоянием, чтобы завершить замкнутый цикл представления, управляемого данными.

Типы enum в Rust очень выразительны, и в сочетании с его мощными возможностями сопоставления с образцом вы получаете абсолютно типобезопасный Redux. Кстати, работая с Typescript, Redux тоже может добиться безопасности типов, но его метод написания сложнее и его нужно делатьгимнастика:), заинтересованные студенты могут прочитать статью, которую я написал«Как использовать типовое программирование типа TypeScript Автоматическое вывод типа редуктора Redux

Как общаются отец и сын

#[derive(Properties, Clone)]
pub struct TodoItemProps {
    pub item: model::TodoItem,
    pub delete: Callback<i32>,
}

impl Component for TodoItem {   
		// ... codes
    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        // 单单针对 state 变化的 shouldComponentUpdate
        // 同时起到一个局部 reducer 的作用
        match msg {
            Msg::OnClick => {
                let id = self.props.item.id.clone();
                self.props.delete.emit(id); // 触发回调
                return  false;
            },
        }
        true
    }
		// ... codes
}

Как и в React, общение между родителем и потомком происходит через функции обратного вызова.<Todo />существует<TodoItem delete={self.link.callback(|id: i32| Msg::DeleteItem(id))} item={item} />изdeleteФункция закрытия устанавливается для свойства, и когда эта функция выполняется дочерним компонентом, ее возвращаемое значениеMsg::DeleteItem(id)будет отправлено в список делupdate()функция, а затем обновить свое состояние и завершить связь

вопрос

У Yew нет хорошего решения для внедрения CSS-файлов, из-за отсутствия инструмента для упаковки, такого как Webpack, вы не можете просто произнести предложение, например, написать JavaScript.import 'index.css'решить. Файлы CSS компонентов в этом проекте вручную вводятся в файл index.html, что я считаю неприемлемым для разработки на основе компонентов.

Других, таких как асинхронные компоненты, встряска дерева и другие вещи, к которым привыкли современные фронтенды, не хватает еще больше, что приводит к тому, что Yew временно остается на игрушечном уровне и не может использоваться в производственной среде. Но будущее все же стоит представить, особенно после выхода DOM API для wasm.

наконец

Вам может быть интересно, какой смысл писать веб-страницы на Rust, я думал о многих причинах, но, в конце концов, это предложение является наиболее убедительным.

"Потому что там гора!"