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

Umbraco+ReactJS.net SSR前端路由切换后嵌套React组件水化问题求助

这问题我之前帮几个做Umbraco+React集成的开发者解决过,核心就是前端路由切换后,动态加载的HTML里的React组件没触发React.NET的初始化——毕竟@Html.ReactInitJavaScript()只在服务器初始渲染时跑一次。给你几个可行的方案,按复杂度和可控性排序:

方案1:让SurfaceController返回初始化所需的完整数据(最直接)

当你调用SurfaceController获取节点网格HTML时,别只返回纯HTML,改成返回包含html和reactInitScripts的JSON对象,复用React.NET的初始化机制。

在SurfaceController里这么处理:

public ActionResult GetPageContent(int nodeId)
{
    var node = Umbraco.Content(nodeId);
    var gridHtml = node.GetGridHtml("content"); // 假设你的网格属性叫content
    
    // 生成React初始化脚本,确保拿到当前请求上下文
    var reactInit = Html.ReactInitJavaScript().ToString();
    
    return Json(new {
        Html = gridHtml.ToString(),
        ReactInitScript = reactInit
    }, JsonRequestBehavior.AllowGet);
}

前端获取数据后,先插入HTML,再动态执行初始化脚本:

// Content组件里的获取数据方法
const fetchPageContent = async (path) => {
    const response = await fetch(`/Surface/PageContent/GetPageContent?nodeId=${getNodeId(path)}`);
    const data = await response.json();
    
    // 插入网格HTML
    setContentHtml(data.Html);
    
    // 动态执行初始化脚本,执行完移除避免重复
    const script = document.createElement('script');
    script.textContent = data.ReactInitScript;
    document.body.appendChild(script);
    script.remove();
};

注意:可以给每个页面的初始化脚本加唯一ID,避免重复执行相同脚本。

方案2:前端提前注册组件,手动触发水化(更可控)

如果不想依赖服务器返回初始化脚本,你可以在前端提前注册所有Umbraco网格用到的自定义组件,加载HTML后手动遍历节点完成水化。

首先在前端全局注册组件:

// 比如在app.js里全局注册
import MyCustomGridComponent from './components/MyCustomGridComponent';
window.ReactComponents = window.ReactComponents || {};
window.ReactComponents.MyCustomGridComponent = MyCustomGridComponent;

然后在加载完HTML后,手动处理未水化的组件:

const hydrateDynamicComponents = () => {
    // 找到所有未标记水化的React组件节点(React.NET会自动加data-react-component属性)
    const reactNodes = document.querySelectorAll('[data-react-component]:not([data-react-hydrated])');
    
    reactNodes.forEach(node => {
        const componentName = node.getAttribute('data-react-component');
        const props = JSON.parse(node.getAttribute('data-react-props'));
        const Component = window.ReactComponents[componentName];
        
        if (Component) {
            ReactDOM.hydrate(<Component {...props} />, node);
            // 标记已水化,避免重复处理
            node.setAttribute('data-react-hydrated', 'true');
        }
    });
};

// 在contentHtml更新后调用水化方法
useEffect(() => {
    if (contentHtml) {
        hydrateDynamicComponents();
    }
}, [contentHtml]);

这个方案完全由前端控制水化过程,适合组件数量固定的场景。

方案3:重构数据返回,避免dangerouslySetInnerHTML(最佳实践)

其实用dangerouslySetInnerHTML加载带React组件的HTML本身就有点绕,更好的方式是让SurfaceController返回网格的结构化数据,前端直接渲染组件。

在Umbraco里把网格数据转成前端能理解的结构:

public ActionResult GetPageContent(int nodeId)
{
    var node = Umbraco.Content(nodeId);
    var gridModel = node.GetPropertyValue<GridModel>("content");
    
    // 把GridModel转成前端可用的结构化数据
    var structuredData = ConvertGridToStructuredData(gridModel);
    
    return Json(structuredData, JsonRequestBehavior.AllowGet);
}

// 自定义转换方法,区分React组件和普通HTML
private object ConvertGridToStructuredData(GridModel grid)
{
    return grid.Sections.Select(section => new {
        section.Grid,
        Rows = section.Rows.Select(row => new {
            row.Grid,
            Areas = row.Areas.Select(area => new {
                area.Grid,
                Widgets = area.Widgets.Select(widget => {
                    // 判断是不是自定义React网格编辑器
                    if (widget.Editor.Alias == "myReactGridEditor") {
                        return new {
                            Type = "ReactComponent",
                            ComponentName = "MyCustomGridComponent",
                            Props = widget.Value // 假设编辑器Value就是组件props
                        };
                    } else {
                        // 普通HTML编辑器返回渲染后的内容
                        return new {
                            Type = "Html",
                            Content = widget.GetValueAsString()
                        };
                    }
                })
            })
        })
    });
}

前端Content组件直接根据结构化数据渲染:

const Content = ({ structuredData }) => {
    return (
        <div className="grid-container">
            {structuredData.map(section => (
                <div key={section.Grid} className="grid-section">
                    {section.Rows.map(row => (
                        <div key={row.Grid} className="grid-row">
                            {row.Areas.map(area => (
                                <div key={area.Grid} className="grid-area">
                                    {area.Widgets.map(widget => {
                                        if (widget.Type === "ReactComponent") {
                                            const Component = window.ReactComponents[widget.ComponentName];
                                            return <Component key={widget.Id} {...widget.Props} />;
                                        } else {
                                            return <div dangerouslySetInnerHTML={{ __html: widget.Content }} />;
                                        }
                                    })}
                                </div>
                            ))}
                        </div>
                    ))}
                </div>
            ))}
        </div>
    );
};

这个方案彻底解决了水化问题,所有React组件都是前端直接渲染的,更符合React开发模式,也更安全。

总结一下:临时解决用方案1,追求可控性选方案2,长期维护优先方案3。

内容的提问来源于stack exchange,提问作者Charlie Anstey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:37:02