refactor(wasm): 消除 DOM 互操作中的 js_sys::eval 字符串求值
对照 Dioxus "Breaking Out" 文档审查后,将 3 处 js_sys::eval 改为
类型化调用,与 tiptap_bridge/codemirror_bridge 风格统一:
- utils/time.rs sleep_ms:eval("new Promise(r=>setTimeout(r,n))")
改为 js_sys::Promise + web_sys::Window::set_timeout_*,复用项目内
system.rs::wasm_sleep 同款写法。
- theme.rs 主题切换:format!-into-eval 拼 __startThemeTransition(x,y)
改为 Reflect::get 取函数 + Function::call2,消除字符串注入面
(原坐标虽为 f64 无实际风险,但属不必要 eval)。
- post_content.rs:__initPostContent / __initLightbox 两处 eval 字面量
改为 invoke_optional_global 辅助函数(Reflect::get + Function::apply),
消除旧版 "eval 字符串粘贴" 模式;__lightboxSelectors 直接复用 JsValue
传入,省去一次回读。
This commit is contained in:
parent
d11385e3ee
commit
fee5b7ba94
@ -4,6 +4,27 @@
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// 读取 `window` 上的可选全局函数并调用;函数未定义/为 null 时静默跳过。
|
||||
///
|
||||
/// 替代 `js_sys::eval("if(window.__x) window.__x(...)")` 字符串拼贴模式:用
|
||||
/// `Reflect::get` 取属性 + `Function::apply` 调用,无字符串求值,与 `tiptap_bridge`
|
||||
/// 的类型化 extern 风格一致。
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn invoke_optional_global(window: &web_sys::Window, name: &str, args: &[wasm_bindgen::JsValue]) {
|
||||
use wasm_bindgen::JsCast;
|
||||
if let Ok(fn_val) = js_sys::Reflect::get(window, &name.into()) {
|
||||
if !fn_val.is_undefined() && !fn_val.is_null() {
|
||||
let arr = js_sys::Array::new();
|
||||
for a in args {
|
||||
arr.push(a);
|
||||
}
|
||||
let _ = fn_val
|
||||
.unchecked_into::<js_sys::Function>()
|
||||
.apply(window, &arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 文章内容组件。
|
||||
///
|
||||
/// Props:
|
||||
@ -18,14 +39,21 @@ use dioxus::prelude::*;
|
||||
pub fn PostContent(content_html: String) -> Element {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use_effect(move || {
|
||||
let _ = js_sys::eval("window.__initPostContent('.post-content')");
|
||||
let window = web_sys::window().unwrap();
|
||||
|
||||
// 调用 window.__initPostContent('.post-content'):函数不存在时静默跳过
|
||||
// (与旧 eval 中的 if 守卫语义一致)。
|
||||
invoke_optional_global(&window, "__initPostContent", &[".post-content".into()]);
|
||||
|
||||
// lightbox 改由 Dioxus.toml 全局 <script src> 加载(不再 include_str!)。
|
||||
// 双保险契约:先设配置,若 lightbox.js 已加载则立即调用;
|
||||
// 双保险契约:先设配置,若 lightbox.js 已加载则立即调用;
|
||||
// 否则 lightbox.js 加载完后其 IIFE 尾部读到配置自启动。
|
||||
let _ = js_sys::eval(
|
||||
"window.__lightboxSelectors = ['.post-content', '.entry-cover']; \
|
||||
if (window.__initLightbox) window.__initLightbox(window.__lightboxSelectors);",
|
||||
);
|
||||
let selectors = js_sys::Array::new();
|
||||
selectors.push(&".post-content".into());
|
||||
selectors.push(&".entry-cover".into());
|
||||
let selectors_val = js_sys::Object::from(selectors).into();
|
||||
let _ = js_sys::Reflect::set(&window, &"__lightboxSelectors".into(), &selectors_val);
|
||||
invoke_optional_global(&window, "__initLightbox", &[selectors_val]);
|
||||
});
|
||||
|
||||
rsx! {
|
||||
|
||||
22
src/theme.rs
22
src/theme.rs
@ -115,7 +115,7 @@ fn detect_initial_theme() -> Theme {
|
||||
}
|
||||
}
|
||||
// 无持久化值:跟随系统。
|
||||
return Theme::System;
|
||||
Theme::System
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
@ -304,25 +304,27 @@ pub fn ThemeToggle() -> Element {
|
||||
let prev_resolved = resolved();
|
||||
let new_resolved = next.resolve(system_dark());
|
||||
if prev_resolved != new_resolved {
|
||||
use wasm_bindgen::JsCast;
|
||||
// 实际明暗翻转 → 触发圆形展开 VT 动画(颜色过渡)。
|
||||
// JS 从 DOM 现状推导目标主题(不传 isDark),避免与 Signal 状态不同步。
|
||||
// 用 Reflect::get 取 window.__startThemeTransition 再 call2 调用,
|
||||
// 替代旧版 format!-into-eval 字符串拼贴(无注入面、与 bridge 风格一致)。
|
||||
let coords = evt.client_coordinates();
|
||||
let x = coords.x;
|
||||
let y = coords.y;
|
||||
let _ = js_sys::eval(
|
||||
&format!(
|
||||
"if (window.__startThemeTransition) \
|
||||
window.__startThemeTransition({x}, {y});",
|
||||
x = x,
|
||||
y = y,
|
||||
),
|
||||
);
|
||||
let window = web_sys::window().unwrap();
|
||||
let key = "__startThemeTransition".into();
|
||||
if let Ok(fn_val) = js_sys::Reflect::get(&window, &key) {
|
||||
if !fn_val.is_undefined() && !fn_val.is_null() {
|
||||
let fn_obj = fn_val.unchecked_into::<js_sys::Function>();
|
||||
let _ = fn_obj.call2(&window, &x.into(), &y.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 立即切换图标:theme.set 触发重渲染换 SVG,配合 key 触发 CSS 进入动画。
|
||||
// VT 的伪元素快照在 __startThemeTransition 内已同步拍好,不受后续真实
|
||||
// DOM 变化影响,故无需推迟 theme.set。
|
||||
theme.set(next);
|
||||
return;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
|
||||
@ -10,15 +10,23 @@
|
||||
use chrono::DateTime;
|
||||
|
||||
/// 异步睡眠指定毫秒数。
|
||||
///
|
||||
/// 用 `js_sys::Promise` + `web_sys::Window::set_timeout_*` 构造,与
|
||||
/// `pages/admin/system.rs::wasm_sleep` 同款写法,避免 `js_sys::eval` 字符串求值。
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn sleep_ms(ms: u32) {
|
||||
use wasm_bindgen::JsCast;
|
||||
let js_code = format!("new Promise(r => setTimeout(r, {}))", ms);
|
||||
if let Ok(promise_val) = js_sys::eval(&js_code) {
|
||||
if let Ok(promise) = promise_val.dyn_into::<js_sys::Promise>() {
|
||||
let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
|
||||
}
|
||||
}
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
let promise = js_sys::Promise::new(&mut |resolve, _| {
|
||||
web_sys::window()
|
||||
.expect("no window")
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
&resolve.unchecked_into(),
|
||||
ms.try_into().unwrap(),
|
||||
)
|
||||
.expect("set_timeout failed");
|
||||
});
|
||||
let _ = JsFuture::from(promise).await;
|
||||
}
|
||||
|
||||
/// 异步睡眠指定毫秒数(原生 tokio 版本)。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user