单页面应用中只有一个root节点,有时会需要挂载一些全局弹窗/toast之类的内容,在root下创建dom,可能会有样式上的影响。本人遇到过的一些坑,例如
transform影响定位的问题。
自定义挂载节点
createPortal方法实现把一些节点渲染到其他root中。
React官网的介绍:
createPortallets you render some children into a different part of the DOM.
TS定义
function createPortal(children: ReactNode, container: Element, key?: null | string): ReactPortal;
简单封装
直接粘贴的arco-design中的Portal组件
git仓库地址: arco-design/arco-design-mobile
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
// arco-design Portal
export interface PortalProps {
/**
* 被挂载的内容
* @en Content to be mounted
*/
children?: React.ReactNode;
/**
* 容器获取函数
* @en Container getter function
* @default () => document.body
*/
getContainer?: () => HTMLElement;
}
/**
* React.createPortal的简单封装。
* @en Simple wrapper for React.createPortal
* @type 其他
* @type_en Others
* @name 自定义挂载
* @name_en Portal
*/
export default function Portal(props: PortalProps) {
const [container, setContainer] = useState<HTMLElement>();
const { children, getContainer } = props;
useEffect(() => {
setContainer(getContainer ? getContainer() : document.body);
}, [getContainer]);
return container ? ReactDOM.createPortal(children, container) : null;
}
多root模式
在需要的时候,新建root节点。利用ReactDOM.createRoot或ReactDOM.render
const createRoot = () => {
const div = document.createElement('div');
div.classList.add(containerClass);
document.body.appendChild(div);
root.root = div;
ReactDOM.render(<App />, root.root);
return div;
};
但是这样的方式会导致新增的组件没办法直接实现对props进行联动,需要一些额外的操作。
interface AppFn {
setState?: (v: AppState) => void;
state?: AppState;
}
interface AppState {
[key: string]: myProps;
}
let root: { root: null | Element } = { root: null };
let key = 0;
const appFn: AppFn = {};
const App = () => {
const [state, setState] = useState<AppState>({});
appFn.setState = setState;
appFn.state = state;
return (
<span>
{keys(state).map((key: string) => {
return <InputReasonModal key={key} {...state[key]} />;
})}
</span>
);
};
const createInputResonModal = (props: myProps) => {
!root.root && createRoot();
const { state = {}, setState } = appFn;
const newProps = { ...props };
newProps.changeVisible = (() => {
let modalKey = key;
return (v: boolean) => {
// 默认为展开 所以一旦关闭,直接销毁
const { state = {}, setState } = appFn;
state[modalKey].visible = false;
setState?.({ ...state });
setTimeout(() => {
console.log(appFn);
const { state = {}, setState } = appFn;
// 销毁组件
delete state[modalKey];
setState?.({ ...state });
// 关闭动画执行完成后,再通知外层
props.changeVisible?.(v);
}, 500);
};
})();
newProps.visible = true;
setState?.({ ...state, [key]: newProps });
key++;
return unmountRoot;
};
const unmountRoot = () => {
unmountComponentAtNode(root.root!);
};