From 69103617c9033ba908f806d315e562a5ab004f90 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Mon, 29 Apr 2024 11:16:24 +0800 Subject: [PATCH 1/5] add blogs --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index fe212d8..69e1496 100644 --- a/readme.md +++ b/readme.md @@ -23,3 +23,7 @@ [从零实现 React v18,但 WASM 版 - [7] 支持 FunctionComponent 类型](https://www.paradeto.com/2024/04/19/big-react-wasm-7/) [从零实现 React v18,但 WASM 版 - [8] 支持 Hooks](https://www.paradeto.com/2024/04/22/big-react-wasm-8/) + +[从零实现 React v18,但 WASM 版 - [9] 使用 Jest 进行单元测试](https://www.paradeto.com/2024/04/23/big-react-wasm-9/) + +[从零实现 React v18,但 WASM 版 - [10] 实现单节点更新流程](https://www.paradeto.com/2024/04/26/big-react-wasm-10/) From 019edd4357c1086afe3f36d7a59407850a5f7842 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Mon, 29 Apr 2024 20:03:36 +0800 Subject: [PATCH 2/5] blog-11: create synthetic_event --- Cargo.lock | 545 ++++++++++++++++++ package.json | 2 +- packages/react-dom/Cargo.toml | 3 +- packages/react-dom/src/host_config.rs | 13 +- packages/react-dom/src/lib.rs | 3 +- packages/react-dom/src/renderer.rs | 8 +- packages/react-dom/src/synthetic_event.rs | 166 ++++++ .../react-reconciler/src/complete_work.rs | 1 + packages/react-reconciler/src/fiber.rs | 2 +- packages/react-reconciler/src/lib.rs | 4 +- packages/shared/src/lib.rs | 4 + 11 files changed, 741 insertions(+), 10 deletions(-) create mode 100644 packages/react-dom/src/synthetic_event.rs diff --git a/Cargo.lock b/Cargo.lock index ec39ddf..535bb5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.5.0" @@ -14,6 +29,12 @@ version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + [[package]] name = "cfg-if" version = "1.0.0" @@ -30,6 +51,331 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gloo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15282ece24eaf4bd338d73ef580c6714c8615155c4190c781290ee3fa0fd372" +dependencies = [ + "gloo-console", + "gloo-dialogs", + "gloo-events", + "gloo-file", + "gloo-history", + "gloo-net", + "gloo-render", + "gloo-storage", + "gloo-timers", + "gloo-utils", + "gloo-worker", +] + +[[package]] +name = "gloo-console" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +dependencies = [ + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" +dependencies = [ + "getrandom", + "gloo-events", + "gloo-utils", + "serde", + "serde-wasm-bindgen", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" +dependencies = [ + "bincode", + "futures", + "gloo-utils", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "js-sys" version = "0.3.69" @@ -39,18 +385,89 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -87,6 +504,7 @@ name = "react-dom" version = "0.1.0" dependencies = [ "console_error_panic_hook", + "gloo", "react-reconciler", "shared", "wasm-bindgen", @@ -107,12 +525,78 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rustversion" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "serde" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "shared" version = "0.1.0" @@ -120,6 +604,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "syn" version = "2.0.53" @@ -131,12 +624,55 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -237,3 +773,12 @@ dependencies = [ "js-sys", "wasm-bindgen", ] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/package.json b/package.json index 97d46ca..ba23706 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "example": "examples" }, "scripts": { - "build": "node scripts/build.js", + "build:dev": "ENV=dev node scripts/build.js", "build:test": "node scripts/build.js --test", "test": "npm run build:test && jest" }, diff --git a/packages/react-dom/Cargo.toml b/packages/react-dom/Cargo.toml index 78d0653..4e5e028 100644 --- a/packages/react-dom/Cargo.toml +++ b/packages/react-dom/Cargo.toml @@ -12,7 +12,7 @@ default = ["console_error_panic_hook"] [dependencies] wasm-bindgen = "0.2.84" -web-sys = { version = "0.3.69", features = ["console", "Window", "Document", "Text", "Element"] } +web-sys = { version = "0.3.69", features = ["console", "Window", "Document", "Text", "Element", "EventListener"] } react-reconciler = { path = "../react-reconciler" } shared = { path = "../shared" } # The `console_error_panic_hook` crate provides better debugging of panics by @@ -20,6 +20,7 @@ shared = { path = "../shared" } # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. console_error_panic_hook = { version = "0.1.7", optional = true } +gloo = "0.11.0" [dev-dependencies] wasm-bindgen-test = "0.3.34" diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index a55ec95..31c71c9 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -1,11 +1,14 @@ use std::any::Any; use std::rc::Rc; +use wasm_bindgen::JsValue; use web_sys::{Node, window}; use react_reconciler::HostConfig; use shared::log; +use crate::synthetic_event::update_event_props; + pub struct ReactDomHostConfig; impl HostConfig for ReactDomHostConfig { @@ -15,11 +18,17 @@ impl HostConfig for ReactDomHostConfig { Rc::new(Node::from(document.create_text_node(content.as_str()))) } - fn create_instance(&self, _type: String) -> Rc { + fn create_instance(&self, _type: String, props: Rc) -> Rc { let window = window().expect("no global `window` exists"); let document = window.document().expect("should have a document on window"); match document.create_element(_type.as_ref()) { - Ok(element) => Rc::new(Node::from(element)), + Ok(element) => { + let element = update_event_props( + element.clone(), + &*props.clone().downcast::().unwrap(), + ); + Rc::new(Node::from(element)) + } Err(_) => todo!(), } } diff --git a/packages/react-dom/src/lib.rs b/packages/react-dom/src/lib.rs index 0e4c3d6..9e8cca5 100644 --- a/packages/react-dom/src/lib.rs +++ b/packages/react-dom/src/lib.rs @@ -12,6 +12,7 @@ use crate::utils::set_panic_hook; mod host_config; mod renderer; mod utils; +mod synthetic_event; #[wasm_bindgen(js_name = createRoot)] pub fn create_root(container: &JsValue) -> Renderer { @@ -24,6 +25,6 @@ pub fn create_root(container: &JsValue) -> Renderer { } }; let root = reconciler.create_container(Rc::new(node)); - let renderer = Renderer::new(root, reconciler); + let renderer = Renderer::new(root, reconciler, container); renderer } diff --git a/packages/react-dom/src/renderer.rs b/packages/react-dom/src/renderer.rs index a79090c..dbf697d 100644 --- a/packages/react-dom/src/renderer.rs +++ b/packages/react-dom/src/renderer.rs @@ -7,21 +7,25 @@ use wasm_bindgen::prelude::*; use react_reconciler::fiber::FiberRootNode; use react_reconciler::Reconciler; +use crate::synthetic_event::init_event; + #[wasm_bindgen] pub struct Renderer { + container: JsValue, root: Rc>, reconciler: Reconciler, } impl Renderer { - pub fn new(root: Rc>, reconciler: Reconciler) -> Self { - Self { root, reconciler } + pub fn new(root: Rc>, reconciler: Reconciler, container: &JsValue) -> Self { + Self { root, reconciler, container: container.clone() } } } #[wasm_bindgen] impl Renderer { pub fn render(&self, element: &JsValue) -> JsValue { + init_event(self.container.clone(), "click".to_string()); self.reconciler .update_container(element.clone(), self.root.clone()) } diff --git a/packages/react-dom/src/synthetic_event.rs b/packages/react-dom/src/synthetic_event.rs new file mode 100644 index 0000000..2fbf331 --- /dev/null +++ b/packages/react-dom/src/synthetic_event.rs @@ -0,0 +1,166 @@ +use gloo::events::EventListener; +use wasm_bindgen::{JsCast, JsValue}; +use wasm_bindgen::closure::Closure; +use web_sys::{Element, Event}; +use web_sys::js_sys::{Function, Object, Reflect}; + +use shared::{derive_from_js_value, is_dev, log}; + +static VALID_EVENT_TYPE_LIST: [&str; 1] = ["click"]; +static ELEMENT_EVENT_PROPS_KEY: &str = "__props"; + +struct Paths { + capture: Vec, + bubble: Vec, +} + +impl Paths { + fn new() -> Self { + Paths { + capture: vec![], + bubble: vec![], + } + } +} + +fn create_synthetic_event(e: Event) -> Event { + Reflect::set(&*e, &"__stopPropagation".into(), &JsValue::from_bool(false)); + + let e_cloned = e.clone(); + let origin_stop_propagation = derive_from_js_value(&*e, "stopPropagation"); + let closure = Closure::wrap(Box::new(move || { + Reflect::set( + &*e_cloned, + &"__stopPropagation".into(), + &JsValue::from_bool(true), + ); + if origin_stop_propagation.is_function() { + let origin_stop_propagation = origin_stop_propagation.dyn_ref::().unwrap(); + origin_stop_propagation.call0(&JsValue::null()); + } + }) as Box); + let function = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + Reflect::set(&*e.clone(), &"stopPropagation".into(), &function.into()); + e +} + +fn trigger_event_flow(paths: Vec, se: &Event) { + for callback in paths { + callback.call1(&JsValue::null(), se); + if derive_from_js_value(se, "__stopPropagation") + .as_bool() + .unwrap() + { + break; + } + } +} + +fn dispatch_event(container: &Element, event_type: String, e: &Event) { + if e.target().is_none() { + log!("Target is none"); + return; + } + + let target_element = e.target().unwrap().dyn_into::().unwrap(); + let Paths { capture, bubble } = + collect_paths(Some(target_element), container, event_type.as_str()); + + let se = create_synthetic_event(e.clone()); + + if is_dev() { + log!("Event {} capture phase", event_type); + } + + trigger_event_flow(capture, &se); + if !derive_from_js_value(&se, "__stopPropagation") + .as_bool() + .unwrap() { + if is_dev() { + log!("Event {} bubble phase", event_type); + } + trigger_event_flow(bubble, &se); + } +} + +fn collect_paths( + mut target_element: Option, + container: &Element, + event_type: &str, +) -> Paths { + let mut paths = Paths::new(); + while target_element.is_some() && Object::is(target_element.as_ref().unwrap(), container) { + let event_props = + derive_from_js_value(target_element.as_ref().unwrap(), ELEMENT_EVENT_PROPS_KEY); + if event_props.is_object() { + let callback_name_list = get_event_callback_name_from_event_type(event_type); + if callback_name_list.is_some() { + for (i, callback_name) in callback_name_list.as_ref().unwrap().iter().enumerate() { + let event_callback = derive_from_js_value(&event_props, *callback_name); + if event_callback.is_function() { + let event_callback = event_callback.dyn_ref::().unwrap(); + if i == 0 { + paths.capture.insert(0, event_callback.clone()); + } else { + paths.bubble.push(event_callback.clone()); + } + } + } + } + } + target_element = target_element.unwrap().parent_element(); + } + paths +} + +fn get_event_callback_name_from_event_type(event_type: &str) -> Option> { + if event_type == "click" { + return Some(vec!["onClickCapture", "onClick"]); + } + None +} + +pub fn init_event(container: JsValue, event_type: String) { + if !VALID_EVENT_TYPE_LIST.contains(&event_type.clone().as_str()) { + log!("Unsupported event type: {:?}", event_type); + return; + } + + if is_dev() { + log!("Init event {:?}", event_type); + } + + let element = container + .clone() + .dyn_into::() + .expect("container is not element"); + let on_click = EventListener::new(&element.clone(), event_type.clone(), move |event| { + dispatch_event(&element, event_type.clone(), event) + }); + on_click.forget(); +} + +pub fn update_event_props(node: Element, props: &JsValue) -> Element { + let js_value = derive_from_js_value(&node, ELEMENT_EVENT_PROPS_KEY); + let element_event_props = if !js_value.is_object() { + js_value.dyn_into::().unwrap() + } else { + Object::new() + }; + for event_type in VALID_EVENT_TYPE_LIST { + let callback_name_list = get_event_callback_name_from_event_type(event_type); + if callback_name_list.is_none() { + break; + } + + for callback_name in callback_name_list.clone().unwrap() { + if element_event_props.has_own_property(&callback_name.into()) { + let callback = derive_from_js_value(props, callback_name); + Reflect::set(&element_event_props, &callback_name.into(), &callback); + } + } + } + Reflect::set(&node, &ELEMENT_EVENT_PROPS_KEY.into(), &element_event_props); + node +} diff --git a/packages/react-reconciler/src/complete_work.rs b/packages/react-reconciler/src/complete_work.rs index 27efe19..0e3d078 100644 --- a/packages/react-reconciler/src/complete_work.rs +++ b/packages/react-reconciler/src/complete_work.rs @@ -145,6 +145,7 @@ impl CompleteWork { .as_ref() .as_string() .unwrap(), + Rc::new(new_props), ); self.append_all_children(instance.clone(), work_in_progress.clone()); work_in_progress.clone().borrow_mut().state_node = diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 2b1c553..0b7b2d0 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -22,7 +22,7 @@ pub enum StateNode { } #[derive(Debug, Clone)] -pub enum MemoizedState { +pub(crate) enum MemoizedState { JsValue(JsValue), Hook(Rc>), } diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 6d30a4b..3651753 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -15,7 +15,7 @@ mod child_fiber; mod commit_work; mod complete_work; pub mod fiber; -pub mod fiber_flags; +mod fiber_flags; mod fiber_hooks; mod update_queue; mod work_loop; @@ -23,7 +23,7 @@ mod work_tags; pub trait HostConfig { fn create_text_instance(&self, content: String) -> Rc; - fn create_instance(&self, _type: String) -> Rc; + fn create_instance(&self, _type: String, props: Rc) -> Rc; fn append_initial_child(&self, parent: Rc, child: Rc); fn append_child_to_container(&self, child: Rc, parent: Rc); fn remove_child(&self, child: Rc, container: Rc); diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index 34ef729..4f4b021 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -19,3 +19,7 @@ pub fn derive_from_js_value(js_value: &JsValue, str: &str) -> JsValue { } } } + +pub fn is_dev() -> bool { + env!("ENV") == "dev" +} From f2cf4314ae0570b5157517aa473a06e6145bdfa4 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Tue, 30 Apr 2024 11:17:54 +0800 Subject: [PATCH 3/5] blog-11: fix synthetic_event bugs --- examples/hello-world/src/App.tsx | 28 +++++++++---------- examples/hello-world/src/main.tsx | 4 +-- packages/react-dom/src/host_config.rs | 7 +++-- packages/react-dom/src/synthetic_event.rs | 15 +++++++--- packages/react-reconciler/src/child_fiber.rs | 5 ++-- .../react-reconciler/src/complete_work.rs | 4 +-- packages/react-reconciler/src/fiber.rs | 4 +-- packages/react-reconciler/src/lib.rs | 2 +- packages/react/src/lib.rs | 14 +++++----- packages/shared/src/lib.rs | 22 +++++++++++++++ 10 files changed, 66 insertions(+), 39 deletions(-) diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 862ad52..2e63abe 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1,24 +1,24 @@ import {useState} from 'react' -let n = 0 function App() { - const [name, setName] = useState(() => false) - const [age, setAge] = useState(() => 10) - if (n === 0) { - let tid = setTimeout(() => { - n++ - setName(true) - setAge(11) - clearTimeout((tid)) - }, 1000) - } + const [num, updateNum] = useState(0); - return name ? {name + age} : 'N/A' + const isOdd = num % 2; + console.log(isOdd) + return ( +

{ + updateNum(prev => prev + 1); + }} + > + {isOdd ?
odd
:

even

} +

+ ); } -function Comp({children}) { - return {`Hello world, ${children}`} +function Child({num}: { num: number }) { + return
{num}
; } export default App diff --git a/examples/hello-world/src/main.tsx b/examples/hello-world/src/main.tsx index 4f2a33c..fd0b0ef 100644 --- a/examples/hello-world/src/main.tsx +++ b/examples/hello-world/src/main.tsx @@ -1,8 +1,6 @@ import {createRoot} from 'react-dom' -import App from './App.tsx' +import App from "./App.tsx"; const root = createRoot(document.getElementById("root")) -const a = -console.log(a) root.render() diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index 31c71c9..ade8810 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use wasm_bindgen::JsValue; use web_sys::{Node, window}; +use web_sys::js_sys::JSON::stringify; use react_reconciler::HostConfig; use shared::log; @@ -12,10 +13,12 @@ use crate::synthetic_event::update_event_props; pub struct ReactDomHostConfig; impl HostConfig for ReactDomHostConfig { - fn create_text_instance(&self, content: String) -> Rc { + fn create_text_instance(&self, content: &JsValue) -> Rc { let window = window().expect("no global `window` exists"); let document = window.document().expect("should have a document on window"); - Rc::new(Node::from(document.create_text_node(content.as_str()))) + Rc::new(Node::from(document.create_text_node( + stringify(content).unwrap().as_string().unwrap().as_str(), + ))) } fn create_instance(&self, _type: String, props: Rc) -> Rc { diff --git a/packages/react-dom/src/synthetic_event.rs b/packages/react-dom/src/synthetic_event.rs index 2fbf331..1803bef 100644 --- a/packages/react-dom/src/synthetic_event.rs +++ b/packages/react-dom/src/synthetic_event.rs @@ -76,7 +76,8 @@ fn dispatch_event(container: &Element, event_type: String, e: &Event) { trigger_event_flow(capture, &se); if !derive_from_js_value(&se, "__stopPropagation") .as_bool() - .unwrap() { + .unwrap() + { if is_dev() { log!("Event {} bubble phase", event_type); } @@ -90,7 +91,7 @@ fn collect_paths( event_type: &str, ) -> Paths { let mut paths = Paths::new(); - while target_element.is_some() && Object::is(target_element.as_ref().unwrap(), container) { + while target_element.is_some() && !Object::is(target_element.as_ref().unwrap(), container) { let event_props = derive_from_js_value(target_element.as_ref().unwrap(), ELEMENT_EVENT_PROPS_KEY); if event_props.is_object() { @@ -143,7 +144,7 @@ pub fn init_event(container: JsValue, event_type: String) { pub fn update_event_props(node: Element, props: &JsValue) -> Element { let js_value = derive_from_js_value(&node, ELEMENT_EVENT_PROPS_KEY); - let element_event_props = if !js_value.is_object() { + let element_event_props = if js_value.is_object() { js_value.dyn_into::().unwrap() } else { Object::new() @@ -155,12 +156,18 @@ pub fn update_event_props(node: Element, props: &JsValue) -> Element { } for callback_name in callback_name_list.clone().unwrap() { - if element_event_props.has_own_property(&callback_name.into()) { + if props.is_object() + && props + .dyn_ref::() + .unwrap() + .has_own_property(&callback_name.into()) + { let callback = derive_from_js_value(props, callback_name); Reflect::set(&element_event_props, &callback_name.into(), &callback); } } } Reflect::set(&node, &ELEMENT_EVENT_PROPS_KEY.into(), &element_event_props); + node } diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index 8bb5def..585fb5d 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use wasm_bindgen::JsValue; use web_sys::js_sys::{Object, Reflect}; -use shared::{derive_from_js_value, log, REACT_ELEMENT_TYPE}; +use shared::{derive_from_js_value, log, REACT_ELEMENT_TYPE, type_of}; use crate::fiber::FiberNode; use crate::fiber_flags::Flags; @@ -131,6 +131,7 @@ fn reconcile_single_text_node( Rc::new(RefCell::new(created)) } + fn _reconcile_child_fibers( return_fiber: Rc>, current_first_child: Option>>, @@ -140,7 +141,7 @@ fn _reconcile_child_fibers( if new_child.is_some() { let new_child = &new_child.unwrap(); - if new_child.is_string() { + if type_of(new_child, "string") || type_of(new_child, "number") { return Some(place_single_child( reconcile_single_text_node( return_fiber, diff --git a/packages/react-reconciler/src/complete_work.rs b/packages/react-reconciler/src/complete_work.rs index 0e3d078..6890113 100644 --- a/packages/react-reconciler/src/complete_work.rs +++ b/packages/react-reconciler/src/complete_work.rs @@ -164,10 +164,8 @@ impl CompleteWork { } } else { let text_instance = self.host_config.create_text_instance( - Reflect::get(&new_props, &JsValue::from_str("content")) + &Reflect::get(&new_props, &JsValue::from_str("content")) .unwrap() - .as_string() - .unwrap(), ); work_in_progress.clone().borrow_mut().state_node = Some(Rc::new(StateNode::Element(text_instance.clone()))); diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 0b7b2d0..efbfeab 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -148,7 +148,7 @@ impl FiberNode { let mut wip = wip_cloned.borrow_mut(); let c = c_rc.borrow(); wip.pending_props = pending_props; - wip.update_queue = Some(c.update_queue.as_ref().unwrap().clone()); + wip.update_queue = c.update_queue.clone(); wip.flags = c.flags.clone(); wip.child = c.child.clone(); wip.memoized_props = c.memoized_props.clone(); @@ -242,8 +242,6 @@ impl Debug for FiberRootNode { current_borrowed.pending_props.as_ref(), &JsValue::from_str("content"), ) - .unwrap() - .as_string() .unwrap(), current_borrowed.flags ) diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 3651753..7cc9ec5 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -22,7 +22,7 @@ mod work_loop; mod work_tags; pub trait HostConfig { - fn create_text_instance(&self, content: String) -> Rc; + fn create_text_instance(&self, content: &JsValue) -> Rc; fn create_instance(&self, _type: String, props: Rc) -> Rc; fn append_initial_child(&self, parent: Rc, child: Rc); fn append_child_to_container(&self, child: Rc, parent: Rc); diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index 4c13620..f23b90b 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -1,4 +1,4 @@ -use js_sys::{Object, Reflect, JSON}; +use js_sys::{JSON, Object, Reflect}; use wasm_bindgen::prelude::*; use shared::REACT_ELEMENT_TYPE; @@ -35,7 +35,7 @@ pub fn jsx_dev(_type: &JsValue, config: &JsValue, key: &JsValue) -> JsValue { &"$$typeof".into(), &JsValue::from_str(REACT_ELEMENT_TYPE), ) - .expect("$$typeof panic"); + .expect("$$typeof panic"); Reflect::set(&react_element, &"type".into(), _type).expect("type panic"); let props = Object::new(); @@ -82,11 +82,11 @@ pub fn is_valid_element(object: &JsValue) -> bool { object.is_object() && !object.is_null() && Reflect::get(&object, &"$$typeof".into()) - .unwrap_or("".into()) - .as_string() - .unwrap_or("".into()) - .as_str() - == REACT_ELEMENT_TYPE + .unwrap_or("".into()) + .as_string() + .unwrap_or("".into()) + .as_str() + == REACT_ELEMENT_TYPE } #[wasm_bindgen(js_name = useState)] diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index 4f4b021..42acc61 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -23,3 +23,25 @@ pub fn derive_from_js_value(js_value: &JsValue, str: &str) -> JsValue { pub fn is_dev() -> bool { env!("ENV") == "dev" } + + +pub fn type_of(val: &JsValue, _type: &str) -> bool { + let t = if val.is_undefined() { + "undefined".to_string() + } else if val.is_null() { + "null".to_string() + } else if val.as_bool().is_some() { + "boolean".to_string() + } else if val.as_f64().is_some() { + "number".to_string() + } else if val.as_string().is_some() { + "string".to_string() + } else if val.is_function() { + "function".to_string() + } else if val.is_object() { + "object".to_string() + } else { + "unknown".to_string() + }; + t == _type +} \ No newline at end of file From 5d1446c632940ebc77ae79fc6b060d482101d90f Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Tue, 30 Apr 2024 11:43:07 +0800 Subject: [PATCH 4/5] blog-11: fix bugs of create_work_in_progress --- Cargo.lock | 1 + examples/hello-world/src/App.tsx | 2 +- packages/react-dom/Cargo.toml | 1 + packages/react-dom/src/host_config.rs | 25 ++++++++++++++++++++++--- packages/react-reconciler/src/fiber.rs | 19 ++++++++++--------- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 535bb5c..5c73bd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,7 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "gloo", + "js-sys", "react-reconciler", "shared", "wasm-bindgen", diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 2e63abe..ecbe143 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -5,7 +5,7 @@ function App() { const [num, updateNum] = useState(0); const isOdd = num % 2; - console.log(isOdd) + return (

{ diff --git a/packages/react-dom/Cargo.toml b/packages/react-dom/Cargo.toml index 4e5e028..23b4c4b 100644 --- a/packages/react-dom/Cargo.toml +++ b/packages/react-dom/Cargo.toml @@ -21,6 +21,7 @@ shared = { path = "../shared" } # code size when deploying. console_error_panic_hook = { version = "0.1.7", optional = true } gloo = "0.11.0" +js-sys = "0.3.69" [dev-dependencies] wasm-bindgen-test = "0.3.34" diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index ade8810..701c5bd 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -1,23 +1,42 @@ use std::any::Any; use std::rc::Rc; +use js_sys::JSON::stringify; use wasm_bindgen::JsValue; use web_sys::{Node, window}; -use web_sys::js_sys::JSON::stringify; use react_reconciler::HostConfig; -use shared::log; +use shared::{log, type_of}; use crate::synthetic_event::update_event_props; pub struct ReactDomHostConfig; +pub fn to_string(js_value: &JsValue) -> String { + js_value.as_string().unwrap_or_else(|| { + if js_value.is_undefined() { + "undefined".to_owned() + } else if js_value.is_null() { + "null".to_owned() + } else if type_of(js_value, "boolean") { + let bool_value = js_value.as_bool().unwrap(); + bool_value.to_string() + } else if js_value.as_f64().is_some() { + let num_value = js_value.as_f64().unwrap(); + num_value.to_string() + } else { + let js_string = stringify(&js_value).unwrap(); + js_string.into() + } + }) +} + impl HostConfig for ReactDomHostConfig { fn create_text_instance(&self, content: &JsValue) -> Rc { let window = window().expect("no global `window` exists"); let document = window.document().expect("should have a document on window"); Rc::new(Node::from(document.create_text_node( - stringify(content).unwrap().as_string().unwrap().as_str(), + to_string(content).as_str() ))) } diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index efbfeab..5fc8db2 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -120,21 +120,17 @@ impl FiberNode { let mut wip = { let c = c_rc.borrow(); let mut wip = FiberNode::new(c.tag.clone(), pending_props, c.key.clone()); - wip.update_queue = match c.update_queue.as_ref() { - None => None, - Some(update_queue) => { - Some(update_queue.clone()) - } - }; + wip._type = c._type.clone(); + wip.state_node = c.state_node.clone(); + + wip.update_queue = c.update_queue.clone(); wip.flags = c.flags.clone(); wip.child = c.child.clone(); wip.memoized_props = c.memoized_props.clone(); wip.memoized_state = c.memoized_state.clone(); + wip.alternate = Some(current); wip }; - wip._type = c_rc.borrow()._type.clone(); - wip.state_node = c_rc.borrow().state_node.clone(); - wip.alternate = Some(current); let wip_rc = Rc::new(RefCell::new(wip)); { let mut fibler_node = c_rc.borrow_mut(); @@ -148,6 +144,11 @@ impl FiberNode { let mut wip = wip_cloned.borrow_mut(); let c = c_rc.borrow(); wip.pending_props = pending_props; + wip.flags = Flags::NoFlags; + wip.subtree_flags = Flags::NoFlags; + wip.deletions = None; + wip._type = c._type.clone(); + wip.update_queue = c.update_queue.clone(); wip.flags = c.flags.clone(); wip.child = c.child.clone(); From 17f6b7a7142e589a401b6dc6056a537ee7efaef8 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Tue, 30 Apr 2024 16:12:37 +0800 Subject: [PATCH 5/5] blog-11: change demo --- examples/hello-world/src/App.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index ecbe143..cbe2731 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -8,11 +8,18 @@ function App() { return (

{ + onClickCapture={(e) => { + e.stopPropagation() + console.log('click h3', e.currentTarget) updateNum(prev => prev + 1); }} > - {isOdd ?
odd
:

even

} +
{ + console.log('click div', e.currentTarget) + }}> + {isOdd ?
odd
:

even

} +
+

); }