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

Preact Redaction组件遍历转换子元素时重复渲染原始内容的问题及优化咨询

Preact Redaction组件遍历转换子元素时重复渲染原始内容的问题及优化咨询

问题定位:虚拟DOM节点构造错误导致的渲染异常

首先,你代码里的redactContent函数在处理非字符串子元素(也就是Preact虚拟DOM节点)时,构造新元素的方式完全不符合Preact的虚拟DOM规范,这就是导致原始内容被额外渲染的核心原因。

你当前的错误逻辑:

const {children, ...rest} = child.props;
return {
 ...rest,
 props: {
  ...child.props,
  children: redactContent(secinfo, level, child.props.children)
 }
};

Preact的虚拟DOM节点是包含type(元素类型,比如"div"或组件函数)、props、key、ref等顶层属性的对象,而你把child.props里的属性(比如class、id)直接展开到返回对象的顶层,又额外嵌套了props字段,这完全是一个无效的虚拟DOM节点结构。Preact无法识别这个错误结构,要么渲染出异常内容,要么 fallback 渲染原始节点,最终导致处理后的内容和原始内容同时出现。


修复方案:正确构造虚拟DOM节点

你需要保留原始虚拟DOM节点的核心属性(type、key、ref等),仅替换children即可。推荐用Preact官方提供的cloneElement工具函数,它能帮你自动处理所有节点属性的复制,避免手动构造出错:

首先导入cloneElement:

import { h, Children, cloneElement } from 'preact';

然后修改redactContent里的虚拟DOM节点处理逻辑:

else if (child.props) {
  return cloneElement(child, {
    children: redactContent(secinfo, level, child.props.children)
  });
}

cloneElement会自动保留原始节点的所有原有属性和结构,你只需要传入要修改的children即可,既简洁又不容易出错。修复后,Preact就能正确渲染处理后的内容,不会再出现原始内容的重复渲染。


更可维护的标准写法优化

你的核心递归遍历思路是对的,但可以从以下几个方面优化,让代码更符合Preact社区最佳实践,提升可维护性:

1. 避免组件内部直接操作DOM

你现在在Redaction组件里直接用document.getElementById('secinfo')读取DOM数据,这会让组件和DOM强耦合,复用性、可测试性都很差。建议让父组件负责获取权限数据,通过props传递给Redaction:

// 父组件使用示例
const secinfo = document.getElementById('secinfo');
const elevatedUsers = JSON.parse(secinfo?.getAttribute('data-elevatedUsers') || '{}');

<Redaction level={2} elevatedUsers={elevatedUsers}>
  {/* 需要处理的内容 */}
</Redaction>

// Redaction组件简化为
interface RedactionProps {
  level: number;
  children: any;
  elevatedUsers: elevatedUserList;
}

export function Redaction(props: RedactionProps) {
  return (
    <div class="redaction" data-level={props.level}>
      {redactContent(props.elevatedUsers, props.level, props.children)}
    </div>
  );
}

2. 增强类型安全

当前代码里children: any的类型太宽泛,建议用Preact提供的ComponentChildren和VNode类型来约束,TypeScript能在编译阶段帮你发现潜在错误:

import type { ComponentChildren, VNode } from 'preact';

interface RedactionProps {
  level: number;
  children: ComponentChildren;
  elevatedUsers: elevatedUserList;
}

function redactContent(secinfo: elevatedUserList, level: number, content: ComponentChildren): ComponentChildren {
  return Children.map(content, (child: VNode | string | null | undefined): ComponentChildren => {
    // 你的处理逻辑
  });
}

3. 处理边界情况

补充对null/undefined/boolean等Preact会自动忽略的类型的处理,避免递归逻辑报错:

function redactContent(secinfo: elevatedUserList, level: number, content: ComponentChildren): ComponentChildren {
  return Children.map(content, (child: VNode | string | null | undefined): ComponentChildren => {
    if (child == null || typeof child === 'boolean') {
      return child;
    }

    if (typeof child === 'string') {
      return isElevated(secinfo, level, true) ? child : child.replace(/\S/g, '█');
    }

    if (child.props) {
      return cloneElement(child, {
        children: redactContent(secinfo, level, child.props.children)
      });
    }

    return child;
  });
}

完整修复后的代码示例

import { h, Children, cloneElement } from 'preact';
import { isElevated } from "../stores/UserStateStore.ts";
import type { elevatedUserList } from "../../../plugins/remark/util/remarkOfWikiLinks-utils.ts";
import type { ComponentChildren, VNode } from 'preact';

interface RedactionProps {
  level: number;
  children: ComponentChildren;
  elevatedUsers: elevatedUserList;
}

function redactContent(secinfo: elevatedUserList, level: number, content: ComponentChildren): ComponentChildren {
  return Children.map(content, (child: VNode | string | null | undefined): ComponentChildren => {
    // 处理Preact会忽略的空值/布尔值
    if (child == null || typeof child === 'boolean') {
      return child;
    }

    // 处理文本节点
    if (typeof child === 'string') {
      return isElevated(secinfo, level, true) ? child : child.replace(/\S/g, '█');
    }

    // 处理虚拟DOM节点
    if (child.props) {
      return cloneElement(child, {
        children: redactContent(secinfo, level, child.props.children)
      });
    }

    return child;
  });
}

export function Redaction(props: RedactionProps) {
  return (
    <div class="redaction" data-level={props.level}>
      {redactContent(props.elevatedUsers, props.level, props.children)}
    </div>
  );
}

这样修改后,不仅解决了重复渲染的问题,代码也更符合Preact的设计理念,可维护性和复用性都有明显提升。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 09:18:01