一些组件更适用于适用createPortal在其他节点生成
这个 API ### 长什么样?createPortal(<></>, otherNode)
一些组件更适用于适用createPortal在其他节点生成
这个 API ### 长什么样?createPortal(<></>, otherNode)
+1。
请问有计划支持吗 @Chen-jj
所以现在是怎样,实现了吗,有计划吗
目前是自己实现了一个,原理就是每个页面包一个container,然后通过useReducer、createContext将portal组件包裹的内容渲染到container里
我参考 ant-design-mobile 的 https://github.com/ant-design/ant-design-mobile-rn/tree/4344e2850727a3fa1c1f7691f362438e2a3a6bfc/components/portal 实现了一个 Portal 组件。
Portal.tsx
import { PropsWithChildren, useContext, useEffect, useState } from "react";
import PortalProvider, { PortalContext } from "./PortalProvider";
import PortalSlot from "./PortalSlot";
const Portal = ({ children }: PropsWithChildren<{}>) => {
const manage = useContext(PortalContext);
const [key, setKey] = useState(0);
// 获取 key
useEffect(() => {
if (manage?.getKey) {
if (!key) {
setKey(manage.getKey());
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [manage?.getKey]);
// 挂载内容
useEffect(() => {
if (key) {
manage?.mount(key, children);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [children, key]);
// 挂载内容
useEffect(() => {
return () => {
if (key) {
manage?.umount(key);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key]);
return <></>;
};
Portal.Provider = PortalProvider;
Portal.Slot = PortalSlot;
export default Portal;
PortalSlot.tsx
import { PortalContext } from "./PortalProvider";
const PortalSlot = () => {
return (
<PortalContext.Consumer>
{manage => {
return (
<>
{/* 展示挂载过来的 portal */}
{Object.keys(manage?.portalChildren || {}).map(key => {
return manage?.portalChildren[key];
})}
</>
);
}}
</PortalContext.Consumer>
);
};
export default PortalSlot;
PortalProvider.tsx
import React, {
PropsWithChildren,
useCallback,
useMemo,
useRef,
useState
} from "react";
interface PortalChildren {
[key: number]: React.ReactNode;
}
interface Manage {
getKey: () => number;
mount: (key: number, children: React.ReactNode) => void;
umount: (key: number) => void;
portalChildren: PortalChildren;
}
export const PortalContext = React.createContext<Manage | undefined>(undefined);
// 提供将 children 挂载到指定位置的能力,配合 PortalSlot 实现
const PortalProvider = ({ children }: PropsWithChildren<{}>) => {
const nextKey = useRef(0);
const [portalChildren, setPortalChildren] = useState<PortalChildren>({});
const mount = useCallback((key: number, c: React.ReactNode) => {
setPortalChildren(currentPortalChildren => {
currentPortalChildren[key] = c;
return { ...currentPortalChildren };
});
}, []);
const umount = useCallback((key: number) => {
setPortalChildren(currentPortalChildren => {
delete currentPortalChildren[key];
return { ...currentPortalChildren };
});
}, []);
const getKey = useCallback(() => {
nextKey.current++;
return nextKey.current;
}, []);
const manage: Manage = useMemo(
() => ({
getKey,
mount,
umount,
portalChildren
}),
[getKey, mount, portalChildren, umount]
);
return (
<PortalContext.Provider value={manage}>{children}</PortalContext.Provider>
);
};
export default PortalProvider;
在页面的外层添加 PortalProvider,在指定挂载的位置放置 PortalSlot,然后就可以在 Modal 之类的组件里面使用 Portal 组件包装,实现在指定的 dom 位置渲染。
<Portal.Provider>
{children}
<Portal.Slot></Portal.Slot>
</Portal.Provider>
<Portal>
<Modal>...</Modal>
</Portal>
有个疑惑,Portal.Provider挂到app.js中的render可以的吗
Taro 已具备 createPortal
的能力,示例如下:
import { useState, useEffect } from 'react'
import { createPortal } from "@tarojs/react";
export default function Index() {
const [dom, setDom] = useState(null);
useEffect(() => {
const dom = document.getElementById("my-portal");
setDom(dom);
setTimeout(() => {
setDom(null);
}, 3000);
}, []);
return (
<View className="index">
<Text>Hello world!</Text>
<View id="my-portal"></View>
{dom && createPortal(<View>你好世界</View>, dom)}
</View>
);
}
其中, dom
必须是 TaroElement
的实例。
同时,微信小程序也提供了 root-portal
组件,原生支持了 Portal 的能力。在 Taro 中使用如下:
import { useState } from 'react'
import { RootPortal, View, Button } from '@tarojs/components'
export default function RootPortalExample {
const [show, setShow] = useState(false)
const toggle = () => {
setShow(!show)
}
render () {
return (
<View>
<Button onClick={toggle}>显示root-portal</Button>
{
show && (<RootPortal><View>content</View></RootPortal>)
}
</View>
)
}
}
依赖createPortal实现root-portal
import { useRouter } from '@tarojs/taro'
import { createPortal } from "@tarojs/react";
import { useLayoutEffect, useState } from "react";
const RootPortal = ({ children, enable = true }) => {
const router = useRouter()
const [dom, setDom] = useState()
useLayoutEffect(() => {
const _dom = document.getElementById(router.$taroPath);
_dom && setDom(_dom)
}, [router.$taroPath])
return (enable && dom) ? createPortal(children, _dom) : children
}
export default RootPortal
请问这种方式小程序支持吗
只要是支持Taro的小程序都支持, 他操作的是react的虚拟dom
啥也不说了,大哥牛逼