You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 06:15:43