如何动态创建 React 组件并附加到另一个 DOM 元素

问题描述 投票:0回答:1

我对 React 有点陌生,我正在尝试在代码中动态创建一个 React 组件,并将其附加到 DOM 元素(在我的

render()
函数之外)。我还想要它的
ref
,以便我可以调用此自定义组件的函数。我面临的主要问题是我无法使用 React
render()
,因为我真的想使用查询 DOM 元素(如
document.querySelector('.grid')
)并将其用作渲染我的 React 自定义组件的目标反应 18.

我尝试将下面的代码与

createElement()
一起使用,但这并没有为组件提供
ref
,而且我也不认为我可以使用它来附加到另一个 DOM 元素。

const myProps = { title: 'My Title' };
const elm = React.createElement(MyCustomComponent, myProps);

然后我尝试使用以下代码

createRef()

const paginationRef = createRef();
const MyComp = forwardRef(() => React.createElement(MyCustomComponent, { ref: paginationRef }));

但是由于我没有调用

render()
,所以它似乎并没有填充
ref

这是一个简单的示例,大致说明了我如何尝试使用它。再说一次,我知道我可以通过

render()
来做到这一点,但这不是我想要做的,我真的想通过任何函数动态地做到这一点。理想情况下,我想将 loadComponentDynamically() 函数移动到单独的 util 服务或函数中以实现可重用,因为我计划在其他一些领域使用它。
import { MyCustomPagination } from './MyCustomPagination';

export class GridComponent extends React.Component {
  loadComponentDynamically() {
    const myProps = { pageSize: 50, totalRows: 5000 };
    const elm = React.createElement(MyCustomPagination, myProps);
    
    // ... what else do I need?
    // I want to append the custom component to gridElm
    const gridElm = document.querySelector('.grid');
  }

  render() {
    // I DO NOT WANT TO USE IT HERE
    return (
     <div class="grid">...</div>
    );
  }
}

我可以通过这个简单的代码轻松地使用 Angular 来做到这一点

export class AngularUtilService { constructor(private vcr: ViewContainerRef) { } createAngularComponent(component, targetElement) { // Create a component reference from the component const componentRef = this.vcr.createComponent(component, createCompOptions); // Get DOM element from component let domElem: HTMLElement | null = null; const viewRef = (componentRef.hostView as EmbeddedViewRef<any>); // get DOM element from the new dynamic Component, make sure this is read after any data and detectChanges() if (viewRef && Array.isArray(viewRef.rootNodes) && viewRef.rootNodes[0]) { domElem = viewRef.rootNodes[0] as HTMLElement; // when user provides the DOM element target, we will read the new Component html and use it to replace the target html if (targetElement && domElem) { targetElement.innerHTML = domElem.innerHTML; } } return { componentRef, domElement }; }

所以如果我能够在 Angular 中做到这一点,我假设必须有一种方法在 React 中做到这一点?我确实必须即时执行此操作并定位 DOM 元素选择器。我知道我可以在 
render()

函数中完成此操作,但这又不是我在这里想要做的,它确实必须是一个 DOM 查询选择器。

编辑
看起来我可能已经找到了部分方法,我不确定这是否是正确的方法?例如,我使用 

queueMicrotask

等待一个周期,但我可以相信一个周期始终足以让我的组件渲染并准备就绪吗?

import { createRoot } from 'react-dom/client'; import { MyCustomPagination } from './MyCustomPagination'; export class GridComponent extends React.Component { loadComponentDynamically(customComponent: any, targetContainer: HTMLElement) { return new Promise(resolve => { const compRef = createRef(); const root = createRoot(targetContainer); root.render(React.createElement(customComponent, { ref: paginationRef })); queueMicrotask(() => { resolve(paginationRef.current); }); }); } }

然后,我还可以调用 

root.unmount()

来处理该组件。

旁注,我知道这是 React 中的反模式,但它仍然是我的用例的最佳方法,因为我支持的库是 JavaScript 库的包装器,因此需要完成某些事情以像这个问题这样的本地方法。

React 组件是 UI 的一部分,具有自己的内部状态和外观;你所追求的,一个本身不提供 UI 的逻辑构建块,不是一个组件。

reactjs
1个回答
0
投票

自定义挂钩让您可以轻松地重用逻辑。您正在做的事情的一个例子:

import { forwardRef, useEffect, useRef } from 'react'; import { createRoot } from 'react-dom/client' function useGridComponent(CustomComponent, targetContainer) { const ref = useRef(null); const initialized = useRef(false); useEffect(() => { if (initialized.current) return; initialized.current = true; createRoot(targetContainer).render(<CustomComponent ref={ref} />); // This cleans up your component -- how you should do so depends on // the exact situation, maybe clearing out the container like this is // fine, maybe you want to just delete some child nodes, etc. Something // for you to decide. return () => targetContainer.innerHTML = ''; }, []); return ref; } // ------- // Usage // ------- const HelloWorld = forwardRef((props, ref) => { return <h1 ref={ref}>HelloWorld</h1>; }) function App() { const gridRef = useGridComponent(HelloWorld, document.getElementById('...')); }

© www.soinside.com 2019 - 2024. All rights reserved.