Примечание: Реактивность и Функции
Один из наших контрибьютеров в ядро сказал мне недавно: — Я никогда не использовал замыкания так часто, пока не начал
использовать Leptos.
И это правда. Замыкания это сердце любого Leptos приложения.
Иногда это выглядит немного глупо:
// a signal holds a value, and can be updated
let (count, set_count) = create_signal(0);
// a derived signal is a function that accesses other signals
let double_count = move || count() * 2;
let count_is_odd = move || count() & 1 == 1;
let text = move || if count_is_odd() {
"odd"
} else {
"even"
};
// an effect automatically tracks the signals it depends on
// and reruns when they change
create_effect(move |_| {
logging::log!("text = {}", text());
});
view! {
<p>{move || text().to_uppercase()}</p>
}
Замыкания, кругом замыкания!
Но почему?
Функции и UI фреймворки
Функции это сердце любого UI фреймворка. И немудрено. Создание пользовательского интерфейса по сути можно разбить на две части:
- первоначальный рендеринг
- обновления
При использовании Веб-фреймворка, именно фреймворк какой-то первоначальный рендеринг. Затем он передаёт контроль обратно браузеру. Когда срабатывают определенные события (такие, как нажатие мыши) или завершаются асинхронные задачи (например, завершается HTTP запрос), браузер будит фреймворк, чтобы обновить что-то. Фреймворк выполняет какой-то код, чтобы обновить интерфейс пользователя, и снова засыпает до тех пор, пока браузер не разбудит его снова.
Ключевой фразой здесь является "выполняет какой-то код". Естественный способ "вызвать какой-то код" в произвольный момент (в Rust или в любом другом языке программирования) это вызвать функцию. И фактически каждый UI фреймворк основан на повторном выполнении некоего рода функции снова и снова:
- фреймворки с виртуальным DOM (VDOM) такие, как React, Yew или Dioxus перезапускают компонент или рендерную функцию снова и снова чтобы сгенерировать виртуальное дерево DOM, которое может быть сверено с предыдущим результатом, чтобы внести правки в DOM
- компилирующие фреймворки как Angular и Svelte разделяют шаблоны компонента на функции "создать" и "обновить", повторно выполняя функцию "обновить" всякий раз, когда они обнаруживают, что состояние компонента изменилось
- во фреймворках с мелкозернистой реактивностью, таких как SolidJS, Sycamore, или Leptos, вы задаёте функции, которые выполняются повторно.
Это то, что все наши компоненты делают.
Возьмём привычный нам пример <SimpleCounter/>
в его простейшей форме:
#[component]
pub fn SimpleCounter() -> impl IntoView {
let (value, set_value) = create_signal(0);
let increment = move |_| set_value.update(|value| *value += 1);
view! {
<button on:click=increment>
{value}
</button>
}
}
Сама по себе функция SimpleCounter
выполняется единожды. Сигнал value
создается один раз. Фреймворк
передаёт функцию increment
браузеру в качестве слушателя событий. При нажатии на кнопку, браузер вызывает increment
,
которая обновляет value
через set_value
. И это обновляет единственный текстовый узел представленный в нашем view
выражением {value}
.
Замыкания это ключ к реактивности. Они дают возможность фреймворку повторно выполнить самый маленький элемент в приложении в ответ на изменение.
Так что помните две вещи:
- Функция компонента это установочная функция, не рендерная функция: она выполняется лишь раз.
- Чтобы значения в шаблоне
view
были реактивными, они должны быть функциями: либо сигналами (они реализуют типажиFn
) или замыканиями.