폼과 이벤트
핸들러는 Dioxus 핸들러 그대로입니다
onclick, oninput, onchange, onsubmit 을 웹에서처럼
씁니다. 클릭은 포인터 아래 요소에 닿아 위로 전파되고, 폼은 브라우저의 폼이 하는 일을 합니다.
입력란은 비제어이므로 타이핑이 Rust 를 왕복하지 않습니다.
클릭
클릭은 그리는 순서와 z-index 로 정해지는 그 자리의 맨 위 요소로 가고, 듣고 있는
가장 가까운 조상까지 전파됩니다. 그다음 브라우저의 기본 동작이 이어집니다.
- 포인터 아래 체크박스는 체크되거나 해제되고, 라디오 버튼은 선택되며, 둘 다
input과change를 보냅니다. - 제출 버튼은 자기 폼을 제출합니다.
label은 자기가 가리키는 컨트롤을 클릭합니다.
체크박스는 클릭 핸들러가 실행되기 전에 바뀌므로 핸들러는 새 상태를 보고, 핸들러 중 하나가
prevent_default() 를 부르면 되돌아갑니다. 링크에서는 samples/hello 처럼
prevent_default() 가 페이지를 그 자리에 둡니다.
입력란은 비제어입니다
사용자가 고치는 동안 입력란의 글자는 렌더러가 갖습니다. Rust 에는 oninput 으로 확정된
글자만 오고, 입력기가 아직 조합 중인 글자는 오지 않습니다. 한글, 일본어, 중국어 입력을 지키는
규칙이 이것입니다. 음절 하나는 여러 번의 키 입력으로 만들어지는데, 그때마다 Rust 에서 되돌려
쓰면 음절이 초기화됩니다.
samples/settings 가 그 방식을 보여 줍니다. 입력란은 저장된 값에서 시작합니다. 친
글자는 아무것도 그것으로 그리지 않는 초안에 들어가므로, 타이핑해도 폼이 다시 그려지지 않고
고치는 중인 입력란에 글자를 되돌려 쓰지도 않습니다.
// What was last saved. The fields start from it.
let mut saved = use_signal(Profile::initial);
// What has been typed since. Nothing renders from it.
let mut draft = use_signal(Profile::initial);
let profile = saved();
rsx! {
form {
id: "settings",
class: "settings",
// Pressing Enter in a field submits the form.
onsubmit: move |event| {
event.prevent_default();
saved.set(draft());
},
input {
id: "name",
class: "control",
r#type: "text",
value: "{profile.name}",
placeholder: "Your name",
oninput: move |event| {
draft.write().name = event.value();
},
}
textarea {
id: "bio",
class: "control",
value: "{profile.bio}",
oninput: move |event| {
draft.write().bio = event.value();
},
}
}
}
입력란은 앱이 value 를 다시 정할 때까지 확정된 글자를 갖고 있고, 폼은 그 값을
제출합니다. 앱이 글자를 바꾸는 방법은 value 를 정하는 것입니다. 예를 들어
samples/chat 과 samples/todo 는 보낸 뒤 입력란을 그렇게 비웁니다.
select, 체크박스, 라디오 버튼
select {
id: "theme",
class: "control",
value: "{profile.theme}",
onchange: move |event| {
draft.write().theme = event.value();
},
option { value: "light", "Light" }
option { value: "dark", "Dark" }
option { value: "system", "Match the system" }
}
input {
id: "weekly",
r#type: "checkbox",
checked: weekly(),
onclick: move |_| weekly.toggle(),
}
for (index, choice) in DENSITIES.into_iter().enumerate() {
label { key: "{choice}", class: "check",
input {
id: "density-{index}",
r#type: "radio",
name: "density",
value: "{choice}",
checked: density() == choice,
onclick: move |_| density.set(choice),
}
"{choice}"
}
}
select 는 입력란 하나이고, 선택지는 렌더러가 자기 드롭다운에 나열하며 페이지 안에는
그리지 않습니다. 하나를 고르면 브라우저처럼 input 다음에 change 가
갑니다. name 이 같은 라디오 버튼은 한 그룹이므로 하나를 고르면 나머지가 해제됩니다.
입력 종류 text, password, email, number,
search, tel, url, checkbox, radio
와 textarea, select 는 각각 종류, 시작 값, placeholder, 글자가 들어갈
상자를 담은 입력란으로 그리기 목록에 들어갑니다.
렌더러가 부르는 것
사용자 동작마다 HtmlDom 의 호출이 하나씩 있습니다. 렌더러가 HTML 화면을 그리게 되면
렌더러가 부릅니다. 지금은 테스트와 임베더가 부르고, 예제의 테스트도 바로 이 호출을 거칩니다.
| 호출 | 사용자 | 앱에 오는 것 |
|---|---|---|
click(x, y) | 한 점을 클릭 | onclick, 그다음 브라우저 기본 동작 |
input(node, value) | 입력란에 글자를 확정 | oninput |
change(node, value) | 입력란을 다 고침 | onchange |
select(node, index) | 선택지를 고름 | oninput, onchange |
check(node, checked) | 체크박스를 체크하거나 라디오를 고름 | onclick, oninput, onchange |
submit(node) | 한 줄 입력란에서 Enter | 제출 버튼이 있으면 그 onclick, 그다음 onsubmit |
assert_eq!(dom.input(name, "Grace Hopper"), Some(name));
assert_eq!(dom.select(theme, 1), Some(theme));
relayout(&mut dom);
// Typing renders nothing: the field's text stays the renderer's.
assert_eq!(entry(&dom, "name").input.as_ref().unwrap().value, "Ada Lovelace");
assert_eq!(dom.submit(name), Some(form));
relayout(&mut dom);
assert_eq!(text_of(entry(&dom, "status")), "Saved");
assert_eq!(entry(&dom, "name").input.as_ref().unwrap().value, "Grace Hopper");
핸들러는 이 호출 안에서 실행됩니다. render 가 핸들러가 바꾼 것을 적용하고, 다음
layout 에 나타납니다.
키가 있는 목록
다른 Dioxus 앱에서처럼 행마다 key 를 줍니다. samples/todo 에서 항목을 위로
옮기면 모든 행의 글자를 다시 쓰는 것이 아니라 그 행이 움직이고, 체크하면 그 행만 바뀌고, 추가하면
행 하나가 들어갑니다. 계획의 diff 는 바로 그 삽입, 삭제, 이동만 알려 주며, 그래서
렌더러 쪽 갱신이 작게 유지됩니다.
ul { id: "todos", class: "todos",
for (index, todo) in items.into_iter().enumerate() {
li {
key: "{todo.id}",
id: "todo-{todo.id}",
class: if todo.done { "todo done" } else { "todo" },
input {
id: "toggle-{todo.id}",
r#type: "checkbox",
checked: todo.done,
onclick: move |_| todos.toggle(todo.id),
}
span { class: "title", "{todo.title}" }
button {
id: "up-{todo.id}",
disabled: index == 0,
onclick: move |_| todos.shift(todo.id, Direction::Up),
"Up"
}
}
}
}
함께 쓰는 상태
samples/todo 의 목록과 samples/chat 의 대화는 앱이 컨텍스트로 제공하는
시그널 구조체에 있습니다. 그래서 항목을 추가하는 행 같은 자식 컴포넌트가
use_context 로 읽습니다. 보통의 Dioxus 이고, 이 렌더러에만 있는 것은 없습니다.