我建议的方法有点冗长,但我发现它可以很好地扩展到复杂的应用程序中。当你想显示一个模态时,触发一个描述你想看到哪个模态的动作:
调度动作以显示模态
this.props.dispatch({
type: 'SHOW_MODAL',
modalType: 'DELETE_POST',
modalProps: {
postId: 42
}
})
(字符串当然可以是常量;为简单起见,我使用内联字符串。)
编写一个 reducer 来管理模态状态
然后确保你有一个只接受这些值的减速器:
const initialState = {
modalType: null,
modalProps: {}
}
function modal(state = initialState, action) {
switch (action.type) {
case 'SHOW_MODAL':
return {
modalType: action.modalType,
modalProps: action.modalProps
}
case 'HIDE_MODAL':
return initialState
default:
return state
}
}
/* .... */
const rootReducer = combineReducers({
modal,
/* other reducers */
})
伟大的!现在,当您调度一个动作时,state.modal
将更新以包含有关当前可见模式窗口的信息。
编写根模态组件
在组件层次结构的根部,添加一个<ModalRoot>
连接到 Redux 存储的组件。它将侦听state.modal
并显示适当的模态组件,从state.modal.modalProps
.
// These are regular React components we will write soon
import DeletePostModal from './DeletePostModal'
import ConfirmLogoutModal from './ConfirmLogoutModal'
const MODAL_COMPONENTS = {
'DELETE_POST': DeletePostModal,
'CONFIRM_LOGOUT': ConfirmLogoutModal,
/* other modals */
}
const ModalRoot = ({ modalType, modalProps }) => {
if (!modalType) {
return <span /> // after React v15 you can return null here
}
const SpecificModal = MODAL_COMPONENTS[modalType]
return <SpecificModal {...modalProps} />
}
export default connect(
state => state.modal
)(ModalRoot)
我们在这里做了什么?ModalRoot
读取它所连接的当前modalType
和modalProps
,并渲染相应的组件,例如or 。每个模态都是一个组件!state.modal
DeletePostModal
ConfirmLogoutModal
编写特定的模态组件
这里没有一般规则。它们只是 React 组件,可以调度动作,从 store 状态读取一些东西,并且恰好是 modals。
例如,DeletePostModal
可能看起来像:
import { deletePost, hideModal } from '../actions'
const DeletePostModal = ({ post, dispatch }) => (
<div>
<p>Delete post {post.name}?</p>
<button onClick={() => {
dispatch(deletePost(post.id)).then(() => {
dispatch(hideModal())
})
}}>
Yes
</button>
<button onClick={() => dispatch(hideModal())}>
Nope
</button>
</div>
)
export default connect(
(state, ownProps) => ({
post: state.postsById[ownProps.postId]
})
)(DeletePostModal)
DeletePostModal
连接到商店,因此它可以显示帖子标题,并且像任何连接的组件一样工作:它可以调度操作,包括在hideModal
需要隐藏自身时。
提取展示组件
为每个“特定”模态复制粘贴相同的布局逻辑会很尴尬。但是你有组件,对吧?因此,您可以提取一个不知道特定模式做什么的表示组件,但可以处理它们的外观。 <Modal>
然后,特定的模态例如DeletePostModal
可以使用它进行渲染:
import { deletePost, hideModal } from '../actions'
import Modal from './Modal'
const DeletePostModal = ({ post, dispatch }) => (
<Modal
dangerText={`Delete post ${post.name}?`}
onDangerClick={() =>
dispatch(deletePost(post.id)).then(() => {
dispatch(hideModal())
})
})
/>
)
export default connect(
(state, ownProps) => ({
post: state.postsById[ownProps.postId]
})
)(DeletePostModal)
由您自己决定一组可以在您的应用程序中接受的道具,<Modal>
但我想您可能有几种模式(例如信息模式、确认模式等),以及它们的几种样式。
可访问性和隐藏单击外部或 Escape 键
关于模态框的最后一个重要部分是,通常我们希望在用户点击外部或按下 Escape 时隐藏它们。
我建议您不要自己实现它,而不是给您关于实现它的建议。考虑到可访问性,很难做到正确。
相反,我建议您使用可访问的现成模态组件,例如react-modal
. 它是完全可定制的,你可以在里面放任何你想要的东西,但它可以正确处理可访问性,以便盲人仍然可以使用你的模态。
你甚至可以react-modal
自己封装<Modal>
,接受特定于你的应用程序的 props 并生成子按钮或其他内容。这一切都只是组件!
其他方法
有不止一种方法可以做到这一点。
有些人不喜欢这种方法的冗长,更喜欢有一个<Modal>
组件,他们可以使用一种称为“门户”的技术在组件内部直接渲染。Portals 让你在你的内部渲染一个组件,而实际上它会在 DOM 中的预定位置渲染,这对于模态框来说非常方便。
事实上react-modal
,我之前链接到的内容已经在内部完成了,所以从技术上讲,你甚至不需要从顶部渲染它。我仍然觉得将我想要显示的模态与显示它的组件分离很好,但你也可以react-modal
直接从你的组件中使用,并跳过我上面写的大部分内容。
我鼓励您考虑这两种方法,对它们进行试验,然后选择最适合您的应用程序和团队的方法。