在 React 不直接管理 DOM 的区域中,可以在浏览器中使用 ReactDOMServer.renderToString 吗?

IT技术 reactjs leaflet react-dom
2021-04-04 00:38:06

我正在使用Leaflet(通过react-leaflet开发一个应用程序Leaflet 直接操作 DOM。react-leaflet 库不会改变这一点,它只是为您提供了 React 组件,您可以使用这些组件以对 React 友好的方式控制您的 Leaflet 地图。

在这个应用程序中,我想使用自定义地图标记,这些标记是包含一些简单元素的 div。在 Leaflet 中这样做的方法是将标记的icon属性设置为DivIcon,您可以在其中设置自定义 HTML。您可以通过将 DivIcon 的html属性设置为包含 HTML 的字符串来设置该内部HTML。就我而言,我希望从 React 组件呈现 HTML。

为了做到这一点,似乎正确的方法是使用ReactDOMServer.renderToString()将我想要在地图标记内的组件呈现为一个字符串,然后我将其设置为htmlDivIcon属性:

MyMarker.js:

import React, { Component } from 'react'
import { renderToString } from 'react-dom/server'
import { Marker } from 'react-leaflet'
import { divIcon } from 'leaflet'

import MarkerContents from './MarkerContents'

export class MyMarker extends Component {
  render() {
    const markerContents = renderToString(<MarkerContents data={this.props.data} />)
    const myDivIcon = divIcon({
      className: 'my-marker',
      html: markerContents
    })

    return (
      <Marker
        position={this.props.position}
        icon={myDivIcon} />
    )
  }
}

但是,根据React 文档

这个 [renderToString] 应该只在服务器上使用。

这是一个严格的规则,还是只是为了劝阻人们不要绕过 ReactDOM 对 DOM 的有效管理?

我想不出另一种(更好的)方法来完成我所追求的事情。任何意见或想法将不胜感激。

4个回答

根据新文档:https : //reactjs.org/docs/react-dom-server.html

以下方法可用于服务器和浏览器环境:

  • 渲染到字符串()
  • renderToStaticMarkup()

我知道这是一个太老的问题,但由于尚未得到解答,我想分享我的想法。

我使用的是同样的东西,renderToString但是由于文档建议不要在客户端使用它,我以另一种方式实现了它,通过使用react-domrender方法将自定义组件呈现为 div

var myDiv = document.createElement('div');

ReactDOM.render(
  <MarkerContents data={this.props.data} />,
  myDiv
);

var myIcon = L.divIcon({ 
    iconSize: new L.Point(50, 50), 
    html: myDiv.innerHTML
});
我已经使用了它,现在我的事件处理程序没有被调用。通过在 js 对象而不是 dom 中渲染组件。我们是否干扰了markerContent 组件的事件绑定?
2021-05-29 00:38:06
我一直这样做直到 React 16 开始进行异步渲染。render在读取innerHTML之前,第三个参数必须是回调函数,如下所示:ReactDOM.render(component, myDiv, () => console.log(myDiv.innerHTML))
2021-06-11 00:38:06

正如 Thomas 已经说过的,是的,您可以renderToString在客户端上使用不过,要清楚的是,您需要ReactDOMServer在客户端上导入,这可能看起来违反直觉,但似乎是正确的。示例(在客户端上):

import React from 'react';
import ReactDOMServer from 'react-dom/server';

const MyComp = (props) => {
  const html = ReactDOMServer.renderToString(<div>{someFunc(props)}</div>);
  // do something with your html, then
  return <div dangerouslySetInnerHTML={{__html: html}}></div>;
};

我在使用传单时遇到了完全相同的问题,最终通过使用门户解决了这个问题

import React, { useEffect, useMemo,useState } from "react";
import ReactDOM from "react-dom";
import { useLeafletContext } from "@react-leaflet/core";
import * as L from "leaflet";

/**
 * @type { React.FC<{
 *    positionX?: number
 *    positionY?: number
 *    width?: number
 *    height?:number
 * }> }
 * */
const LeafletChild = ({ children, positionX=0, positionY=0, width, height }) => {
  const context = useLeafletContext();
  const [divElement] = useState(() => document.createElement("div"));
  const icon = useMemo(() => L.divIcon({ iconSize: new L.Point(height, width), html: divElement }), [height,width, divElement]);
  const marker = useMemo(() => L.marker([0,0], { icon }), [icon]);
  useEffect(()=>{
    marker.setLatLng([positionY, positionX])
  },[positionY,positionX, marker])

  useEffect(() => {
    const container = context.layerContainer || context.map;
    container.addLayer(marker);
    return () => container.removeLayer(marker);
  }, [context, marker]);
  return ReactDOM.createPortal(children, divElement);
};

别的地方...

<LeafletChild>
  <div onClick={()=>setSomeBool(!someBool)}>
    anything you want here like...{`${someBool}`}
  </div>
</LeafletChild>