Open worldzhao opened 2 years ago
在中后台业务中归纳出这样的方法,将组件 promise 化:
function promisify(Render) {
return new Promise((res, rej) => {
const div = document.createElement('div')
document.body.appendChild(div)
ReactDOM.render(
<Render
destroy={val => {
if (val) res(val)
else rej()
ReactDOM.unmountComponentAtNode(div)
document.body.removeChild(div)
}}
/>,
div
)
})
}
在中后台业务中归纳出这样的方法,将组件 promise 化:
function promisify(Render) { return new Promise((res, rej) => { const div = document.createElement('div') document.body.appendChild(div) ReactDOM.render( <Render destroy={val => { if (val) res(val) else rej() ReactDOM.unmountComponentAtNode(div) document.body.removeChild(div) }} />, div ) }) }
这个方法很赞!我们也有类似的工具函数,对于 99% 场景都足够了(也没遇到无法支撑的场景),主要是去年看到了 NiceModal,调用方式更加灵活,也解决了 Context 问题,就换 NiceModal 了。
这个方法很赞!我们也有类似的工具函数,对于 99% 场景都足够了(也没遇到无法支撑的场景),主要是去年看到了 NiceModal,调用方式更加灵活,也解决了 Context 问题,就换 NiceModal 了。
以前的我:这样写是不是违反了 React 伦理,会不会坏了祖师爷立下的规矩 现在的我:去 tm 的 Thinking in React
在中后台业务中归纳出这样的方法,将组件 promise 化:
function promisify(Render) { return new Promise((res, rej) => { const div = document.createElement('div') document.body.appendChild(div) ReactDOM.render( <Render destroy={val => { if (val) res(val) else rej() ReactDOM.unmountComponentAtNode(div) document.body.removeChild(div) }} />, div ) }) }
这个方法很赞!我们也有类似的工具函数,对于 99% 场景都足够了(也没遇到无法支撑的场景),主要是去年看到了 NiceModal,调用方式更加灵活,也解决了 Context 问题,就换 NiceModal 了。
这个挂载 body 可能会有问题哦,比如在微前端的场景下,看看是不是应该弄一个类似 antd 的 getPopupContainer
@enclairfarron indeed
ReactDOM.render
是否改成ReactDOM.createPortal
会更好一些
我发现很多同学对 ReactDOM.createPortal
这个方法理解有一些偏差,这个方法返回的 portal 元素依旧是需要挂载的(ReactDOM.render 调用执行就能渲染),你可以把返回的 portal 也理解成一个组件,是 Modal 和 Popup 等组件的底层,但不适用于本文面对和解决的问题。
在开始进入正题之前,先看看一些与弹窗有关的有趣场景 🤔。
一些有趣的真实场景
案例一:全局弹窗
上图是掘金的登录弹窗,未登录状态下触发该弹窗展示的时机有很多,比如:
开发者往往会基于第三方组件库定义一个
<LoginModal />
,然后将其挂载至Root
组件下:这样会带来一些问题:
<LoginModal />
内部逻辑在组件渲染的时候就会执行,即使弹窗处于隐藏状态setVisible
以及setOtherLoginData
透传至<Main />
内部的多个子组件,你可以选择通过 props 一层一层的传递进去(鬼知道有多少层!),也可以引入工具进行状态共享,但不论怎样,这都不是一件容易的事;展示一个弹窗,为什么会变得如此复杂?
除了上述全局弹窗的场景,还有一种场景也很让人头疼。
案例二:存在分支以及依赖关系的弹窗
弹窗 1 确认弹出弹窗 2,取消则弹出弹窗 3,弹窗 2 以及 弹窗 3 也存在对应的分支逻辑,子孙满堂轻而易举,若按照常规声明式弹窗组件的实现,非常恐怖!
不那么通用的解决方案
摆脱所谓的 React 哲学:数据驱动视图(
view = f(data)
),回归原始的window.confirm
以及window.alert
,很多问题迎刃而解:经过以上代码处理,可以直接通过
LoginModal.show({otherLoginData})
展示弹窗(hide
以及destroy
同理)。对于存在依赖关系的弹窗,则可以通过返回
Promise
进行链式调用,使用方式如下,此处不过多展开。由于该方案是通过
ReactDOM.render
将组件渲染到一个新节点,脱离了原始的 React 上下文,在某些强依赖Context
的场景会显得有些麻烦。可能是最好的弹窗实践
某天在逛 github 的时候发现了
@ebay/nice-modal-react
,让我们看看 ebay 的开发者是如何让弹窗在 React 下变得足够 Nice。创建弹窗
和原先基于 antd 自定义一个弹窗组件非常相似,只不过弹窗显隐相关的
props
(如visible/hide/remove
) 是通过useModal
获取,最外层则通过NiceModal.create
将组件进行一层封装(高阶组件)。使用弹窗
在使用弹窗之前,需要在
Root
节点引入<NiceModal.Provider />
随后便可在任意地方通过
NiceModal.show
展示先前自定义的弹窗:经过
NiceModal
的封装,好处显而易见:Provider
下)show/hide
方法返回值为Promise
,方便链式调用(通过useModal().resolve(val)
等方法改变Promise
状态)简单实现思路
如果你对 NiceModal 的实现原理有兴趣,那么可以思考一下我们之前的痛点是什么?案例一中描述了两大主要痛点:
NiceModal.Provider
内统一渲染弹窗show/hide/useModal
)暴露理解上述三个方法后,
show/hide/useModal
等方法就是基于dispatch
函数进行弹窗数据(Context
) 的更新从而触发视图更新(其中show
方法多一步注册,生成 ID 并绑定至对应的高阶组件)。推荐阅读