Modal in
React and Redux
—— 刘旸 @外居乐
噩梦
问题
- 管理显示逻辑很麻烦
- 数量太多有性能问题
- 逻辑与上下文相关
基础的 Modal
import React from 'react';
import Modal from 'react-modal';
import ReactDOM from 'react-dom';
Modal.setAppElement('#app');
const App = () => (
<div>
<Modal
isOpen
onAfterOpen={...}
onRequestClose={...}
style={...}
contentLabel="Opened Modal"
>
Opened Modal
</Modal>
<Modal
isOpen={false}
onAfterOpen={...}
onRequestClose={...}
style={...}
contentLabel="Closed Modal"
>
Closed Modal
</Modal>
</div>
)
ReactDOM.render(<App />, document.getElementById('app'));
<html lang="en">
<head>...</head>
<body class="ReactModal__Body--open">
<div id="app" aria-hidden="true">...</div>
<div class="ReactModalPortal">
<div class="Overlay">
<div aria-label="Opened Modal" tabindex="-1">
Opened Modal
</div>
</div>
</div>
<div class="ReactModalPortal"></div>
</body>
</html>
- Append 到 Root 节点的外面
- 只有 Open 的时候才会有 children
业务场景下的 Modal
- 只在局部使用
- 在一个页面中复用
- 在多个页面中复用
只在局部使用的 Modal
- Modal 和打开的按钮一起写在使用的组件中
- 状态放在该组件的 state 中
import React from 'react';
import Modal from 'react-modal';
import { compose, withState, pure } from 'recompose';
export default compose(
pure,
withState('isOpen', 'setIsOpen', false),
)(({ isOpen, setIsOpen }) => (
<div>
<Modal
isOpen={isOpen}
onRequestClose={() => setIsOpen(false)}
>
Modal
</Modal>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
</div>
));
在页面中会复用的 Modal
- 例如:修改列表中某一项的 Modal
- Modal 写在父组件中
- 状态放在父组件的 state 中
- 修改状态的方法传递给弹出 Modal 的子组件
import React from 'react';
import Modal from 'react-modal';
import { compose, withState, pure } from 'recompose';
const Item = ({ openModal }) => (
<div>
<button onClick={openModal}>Open Modal</button>
</div>
);
export default compose(
pure,
withState('isOpen', 'setIsOpen', false),
withState('currentItem', 'setCurrentItem', {}),
)(({ isOpen, setIsOpen, items, setCurrentItem, currentItem }) => (
<div>
<Modal
isOpen={isOpen}
onRequestClose={() => setIsOpen(false)}
>
Selected Item: {currentItem.id}
</Modal>
{items.map(item => (
<Item
key={item.id}
openModal={() => {
setIsOpen(true);
setCurrentItem(item);
}}
/>
))}
</div>
));
在多页面中会复用的 Modal
- 例如:Login / Register Modal
- Modal 写在 Layout 组件中
- 状态放在 store 中
- 修改状态的 action 传递给弹出 Modal 的子组件
复杂业务场景下的 Modal
Confirmation
- 页面中可能同时存在多个 Confirmation
- 选择确定或取消有后续操作
- 后续操作有可能是异步的,需要 Loading
- 我只想调用一个方法就把 Modal 打开
- 我希望可以自动 Loading,完成以后关闭
实现方案 —— Antd
- 提供一个 static 方法来打开 Modal
- 使用 ReactDOM API、DOM API 控制 DOM
- 确认的时候检查 OK callback,如果是 promise 的话就显示 Loading,resolve 以后才关闭
实现方案 —— Redux
- Store 中存储 confirmations 列表
- callback 存放在哪里?
- 如何处理 loading ?
import { OPEN_CONFIRMATION, CONFIRMATION_OK, CONFIRMATION_CANCEL } from 'action-types';
import { startConfirmationLoading } from 'actions';
export default () => {
const callbacks = [];
return ({ dispatch }) => (next) => async (action) => {
const { payload, type } = action;
if (type === OPEN_CONFIRMATION) {
const { onOK, onCancel } = payload;
callbacks.push({ onOK, onCancel });
} else if (type === CONFIRMATION_OK || type === CONFIRMATION_CANCEL) {
const { onOK, onCancel } = callbacks.pop();
const result = type === CONFIRMATION_OK ? onOK() : onCancel();
if (isPromise(result)) {
dispatch(startConfirmationLoading());
await result;
return next(action);
}
}
return next(action);
};
}
Login
- Login 成功或失败以后有后续操作
- 后续操作需要依赖于现有数据或 API 返回数据
import { OPEN_LOGIN_MODAL, CLOSE_LOGIN_MODAL } from 'action-types';
import { prop } from 'lodash/fp';
export default () => {
let loginPromise = null;
let resolveLogin = null;
let rejectLogin = null;
return ({ getState }) => (next) => (action) => {
const { type, payload } = action;
if (type === OPEN_LOGIN_MODAL) {
loginPromise = new Promise((resolve, reject) => {
resolveLogin = resolve;
rejectLogin = reject;
});
next(action);
return loginPromise;
}
if (type === CLOSE_LOGIN_MODAL) {
if (loginPromise) {
if (payload.success) {
resolveLogin({
payload: prop('currentAccount.accountId')(getState()),
});
} else {
rejectLogin();
}
loginPromise = null;
}
}
return next(action);
};
};
总结
- 只需要调用一个 action 来打开共用的 Modal
- 使用 Store 来存储共用 Modal 的可序列化状态
- 使用 Middleware 来存储不可序列化的 callback
问题
- Login 后续操作依赖的数据可能会变多
Modal in React and Redux
By zation
Modal in React and Redux
- 1,492