Deno中如何让客户端执行服务器构建的重渲染、事件监听等函数?
解决方案:将函数式响应逻辑传递到客户端执行
针对你的类JSX函数式UI库,要把重渲染逻辑、事件监听器这类客户端代码从服务端传递到浏览器执行,核心思路是序列化函数元数据+客户端运行时解析执行,结合你已有的data-ref和JSON关联方案,具体实现步骤如下:
1. 扩展响应关联JSON结构
在原有状态关联信息的基础上,加入客户端脚本的元数据(不用直接存函数,存函数标识和参数):
// 扩展后的响应关联JSON [ { reactant: 'count', rerender: 'child', elementRef: '1', // 对应HTML的data-ref属性 // 事件监听器元数据 eventListeners: [ { type: 'click', handlerKey: 'incrementCount', // 客户端运行时的函数标识 args: ['count'] // 传递给函数的参数 } ], // 自定义重渲染函数标识(如果有通用逻辑之外的需求) customRerender: null } ]
2. 服务端注入数据与运行时脚本
服务端渲染HTML时,把以下内容嵌入页面:
- 初始状态(
__APP_INITIAL_STATE__) - 扩展后的响应关联元数据(
__APP_REACTIVE_META__) - 自定义函数的代码字符串(
__APP_HANDLERS__) - 客户端核心运行时脚本(可以内嵌或通过外部文件引入)
示例HTML输出:
<span data-ref="1">0</span> <script> // 初始状态 window.__APP_INITIAL_STATE__ = {"count": 0}; // 响应关联元数据 window.__APP_REACTIVE_META__ = [{"reactant":"count","rerender":"child","elementRef":"1","eventListeners":[{"type":"click","handlerKey":"incrementCount","args":["count"]}],"customRerender":null}]; // 自定义函数集合 window.__APP_HANDLERS__ = { incrementCount: function(state, element, args) { state[args[0]]++; } }; </script> <script src="/client-runtime.js"></script>
3. 客户端运行时核心实现
客户端运行时负责解析元数据、绑定事件、订阅状态变化并执行重渲染,以下是核心逻辑示例:
// client-runtime.js class ObservableState { constructor(initialState) { this.state = new Proxy(initialState, { set: (target, prop, value) => { target[prop] = value; this.notify(prop); return true; } }); this.subscribers = new Map(); // key: 状态变量名, value: 回调数组 } subscribe(reactant, handler) { if (!this.subscribers.has(reactant)) { this.subscribers.set(reactant, []); } this.subscribers.get(reactant).push(handler); } notify(reactant) { this.subscribers.get(reactant)?.forEach(handler => handler()); } } // 初始化流程 const initialState = window.__APP_INITIAL_STATE__; const reactiveMeta = window.__APP_REACTIVE_META__; const handlers = window.__APP_HANDLERS__ || {}; const appState = new ObservableState(initialState); // 遍历元数据,绑定事件+订阅状态 reactiveMeta.forEach(entry => { const element = document.querySelector(`[data-ref="${entry.elementRef}"]`); if (!element) return; // 绑定事件监听器 entry.eventListeners?.forEach(listener => { const handler = handlers[listener.handlerKey]; if (handler) { element.addEventListener(listener.type, () => { handler(appState.state, element, listener.args); }); } }); // 订阅状态变化,执行重渲染 appState.subscribe(entry.reactant, () => { const currentValue = appState.state[entry.reactant]; switch(entry.rerender) { case 'child': element.textContent = currentValue; break; case 'custom': handlers[entry.customRerender]?.(element, currentValue); break; // 其他重渲染类型的处理逻辑 } }); });
4. 服务端收集元数据的逻辑补充
在服务端执行组件渲染时,需要在生成HTML的同时收集响应式元数据。比如修改你的createApp和元素创建函数(a.span等),加入元数据收集逻辑:
// 服务端示例:createApp函数 function createApp(component) { const state = { count: 0 }; const metaCollector = []; // 假设a.span等函数会接收metaCollector,存入当前元素的响应式信息 const html = component(state, metaCollector); // 生成最终HTML return ` <div id="app">${html}</div> <script> window.__APP_INITIAL_STATE__ = ${JSON.stringify(state)}; window.__APP_REACTIVE_META__ = ${JSON.stringify(metaCollector)}; window.__APP_HANDLERS__ = { incrementCount: ${incrementCount.toString()} }; </script> <script src="/client-runtime.js"></script> `; } // 服务端定义的自定义事件处理函数 function incrementCount(state, element, args) { state[args[0]]++; }
关键注意事项
- 避免闭包问题:不要直接序列化带闭包的函数,所有依赖的变量都要通过
args参数传递或存入全局状态。 - 安全性:如果允许用户自定义函数,要过滤不可信代码,避免XSS风险。
- 性能优化:客户端运行时可以缓存
data-ref与元素的映射,减少DOM查询次数;重渲染逻辑尽量轻量化,只更新必要的DOM节点。
内容的提问来源于stack exchange,提问作者Oscar Alfonso Guerrero
相关产品推荐
相关产品推荐

