我对 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 的逻辑构建块,不是一个组件。
自定义挂钩让您可以轻松地重用逻辑。您正在做的事情的一个例子:
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('...'));
}