How to event in React
前回の Stateful React via Event を simple に updateしたものです。 event listener のように state 管理します。 event test - CodeSandbox から demo をあそぶことができます
event について
各 event key の Set
に mount / clean
で function を add / delete します。
obj
をつかって f("a", "b")
と f({ a: "b" })
が同様にうごくようにしました。
function event() {
const map = new Map();
const set = (key) => map.get(key) || map.set(key, new Set()).get(key);
const ret = obj((key, ..._) => set(key).forEach((l) => l(ret, ..._)));
ret.mount = obj((key, fun) => set(key).add(fun));
ret.clean = obj((key, fun) => set(key).delete(fun));
return ret;
}
obj
function obj(fun = () => {}) {
return (target, ...args) => {
if (typeof target === "string") fun(target, ...args);
else for (const key in target) fun(key, target[key]);
};
}
Getting started
今回も、KeyEventで文字列を変更するアプリをつくりました。
keydown
event の key と同じ event を実行するために、mount
で登録します。
createRoot(document.getElementById("root")).render(
<KeyInput
mount={(key) => window.addEventListener("keydown", (e) => key(e.key))}
clean={(key) => window.removeEventListener("keydown", (e) => key(e.key))}
a={(key) => key(" ", "A")}
b={(key) => key(" ", "B")}
// c to z
/>
);
props
から、 KeyInput
に指定された function が add / delete されます。
key
は immutable なので、 useEffect
は mount / clean の一回のみ実行されます。
function KeyInput({ children, ...other }) {
const [text, set] = useState("");
const key = useEvent({
Enter: () => set(""),
Backspace: () => set((p) => p.slice(0, -1)),
" ": (_key,_ = "_") => set((p) => p +_),
...other
});
useEffect(() => void key("mount"), [key]);
useEffect(() => () => key("clean"), [key]);
return text || `PRESS_KEYBOARD`;
}
useEvent
useEffect
は mount / clean でのみ実行され、 functionの add / delete をします。
useMutable
は不要かもですが、二重追加をふせぐために、 function を固有にします。
function useEvent(_ref, target) {
const ref = useMutable(_ref);
const ret = useMemo(() => target || event(), [target]);
useEffect(() => void ret.mount(ref), [ret, ref]);
useEffect(() => () => ret.clean(ref), [ret, ref]);
return ret;
}
useMutable
function useMutable(target) {
const ref = useRef();
ref.current = target;
return useMemo(() => {
const ret = {};
for (const key in target)
ret[key] = ret[key] || ((...args) => ref.current[key](...args));
return ret;
}, [ret, target]);
}
migrate to solid.js
solid.jsに移植してみました。 codesandbox であそぶことができます。
import html from "https://cdn.skypack.dev/solid-js/html";
import { render } from "https://cdn.skypack.dev/solid-js/web";
import { createSignal, createMemo, onCleanup, onMount } from "https://cdn.skypack.dev/solid-js";
import { event } from "https://cdn.skypack.dev/reev";
function useEvent(ref, target) {
const ret = createMemo(() => target || event());
onMount(() => void ret().mount(ref));
onCleanup(() => void ret().clean(ref));
return ret;
}
const other = { a: (key) => key(" ", "A") } // a to z
function KeyInput() {
const [text, set] = createSignal("");
const ret = createMemo(() => text() || "PRESS_KEYBOARD");
const key = useEvent({
...other,
Enter: () => set("PRESS_KEYBOARD"),
Backspace: () => set((p) => p.slice(0, -1)),
" ": (_key, _ = "_") => set((p) => p + _)
});
const handler = (e) => key()(e.key);
onMount(() => void window.addEventListener("keydown", handler));
onCleanup(() => void window.removeEventListener("keydown", handler));
return html`<div>${ret}</div>`;
}
render(KeyInput, document.body);
migrate to pure.js
createAtom
だけで十分うごきました。 codesandbox であそぶことができます。
<body>
<script type="module">
import { event } from "https://cdn.skypack.dev/reev";
const div = document.getElementById("div");
const set = (fun) => (div.textContent = fun(div.textContent));
const key = event();
const other = { a: (key) => key(" ", "A") } // a to z
window.addEventListener("keydown", (e) => key(e.key));
key.mount({
...other,
Enter: () => set(() => "PRESS_KEYBOARD"),
Backspace: () => set((p) => p.slice(0, -1)),
" ": (_key, _ = "_") => set((p) => p + _)
});
</script>
<div id="div">PRESS_KEYBOARD</div>
</body>