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

React中如何优雅渲染带样式的HTML片段响应?

问题描述

我在React项目中通过fetch调用以下接口:

curl --location 'https://example.com/some/v1'

--header 'Content-Type: text/html'

接口返回包含多个<style>标签与HTML结构的响应(部分内容如下):

<style class="ssr-css">
    .some_class {
        margin: 0 auto
    }

    html,
    body {
        overflow-x: hidden;
        width: 100%;
        height: 100%
    }
    /* 100+ more styles */
</style>
<style class="ssr-css">
    .some_class_1 {
        margin: 0 auto
    }
    /* 600+ more styles */
</style>
<div class="some_class">
    <div style="color: red" class="some_class_1">
        some text
    </div>
    <!-- 1000's more lines -->
</div>

我希望将其转换为React组件渲染,但直接用dangerouslySetInnerHTML只能处理HTML,样式不生效。我尝试过提取样式注入document的方案(代码如下),但不够优雅,还担心影响页面其他样式:

import React, { useEffect, useState } from 'react';

const MyComponent = () => {
  const [htmlResponse, setHtmlResponse] = useState('');

  useEffect(() => {
    fetch('https://example.com/some/v1', {
      headers: {
        'Content-Type': 'text/html',
      },
    })
      .then((response) => response.text())
      .then((data) => setHtmlResponse(data))
      .catch((error) => console.error(error));
  }, []);

  useEffect(() => {
    const styleRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gm;
    const matches = [...htmlResponse.matchAll(styleRegex)];
    const styles = matches.map((match) => match[1]).join('');

    const styleElement = document.createElement('style');
    styleElement.innerHTML = styles;
    document.head.appendChild(styleElement);
  }, [htmlResponse]);

  return (
    <div dangerouslySetInnerHTML={{ __html: htmlResponse }} />
  );
};

export default MyComponent;

有没有更简洁的React实现方式,无需直接将样式注入document?


解决方案1:用React原生<style>组件同层级渲染样式

核心思路是分离响应中的样式与HTML内容,将提取的样式用React原生<style>组件渲染,和HTML内容放在同一层级。这种方式避免了直接操作document.head,样式作用域更贴近组件(注意:全局选择器如html/body仍会影响全局)。

import React, { useEffect, useState } from 'react';

const MyComponent = () => {
  const [styles, setStyles] = useState('');
  const [contentHtml, setContentHtml] = useState('');

  useEffect(() => {
    fetch('https://example.com/some/v1', {
      headers: { 'Content-Type': 'text/html' },
    })
      .then(res => res.text())
      .then(data => {
        // 提取所有style标签内的样式内容
        const styleRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gm;
        const extractedStyles = [...data.matchAll(styleRegex)].map(match => match[1]).join('');
        // 移除所有style标签,保留纯HTML内容
        const cleanedHtml = data.replace(styleRegex, '');
        
        setStyles(extractedStyles);
        setContentHtml(cleanedHtml);
      })
      .catch(err => console.error(err));
  }, []);

  return (
    <div>
      <style>{styles}</style>
      <div dangerouslySetInnerHTML={{ __html: contentHtml }} />
    </div>
  );
};

export default MyComponent;

解决方案2:用Shadow DOM彻底隔离样式

如果需要完全避免样式污染(包括全局选择器的影响),可以使用Shadow DOM将组件的DOM和样式完全隔离,响应中的所有样式只会作用于Shadow DOM内部。

import React, { useEffect, useRef, useState } from 'react';

const MyComponent = () => {
  const containerRef = useRef(null);
  const [htmlResponse, setHtmlResponse] = useState('');

  useEffect(() => {
    fetch('https://example.com/some/v1', {
      headers: { 'Content-Type': 'text/html' },
    })
      .then(res => res.text())
      .then(data => setHtmlResponse(data))
      .catch(err => console.error(err));
  }, []);

  useEffect(() => {
    if (!containerRef.current || !htmlResponse) return;

    // 创建Shadow DOM并插入完整响应内容
    const shadowRoot = containerRef.current.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = htmlResponse;
  }, [htmlResponse]);

  return <div ref={containerRef} />;
};

export default MyComponent;

解决方案3:用第三方库简化HTML解析

不想手动处理正则的话,可以使用react-html-parser库,它会自动将HTML字符串转换为React元素,包括<style>标签的处理,代码更简洁。

首先安装依赖:

npm install react-html-parser

组件实现:

import React, { useEffect, useState } from 'react';
import ReactHtmlParser from 'react-html-parser';

const MyComponent = () => {
  const [htmlResponse, setHtmlResponse] = useState('');

  useEffect(() => {
    fetch('https://example.com/some/v1', {
      headers: { 'Content-Type': 'text/html' },
    })
      .then(res => res.text())
      .then(data => setHtmlResponse(data))
      .catch(err => console.error(err));
  }, []);

  return <div>{ReactHtmlParser(htmlResponse)}</div>;
};

export default MyComponent;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 01:04:54