我建议的方法有点啰嗦,但我发现它可以很好地扩展到复杂的应用程序。当你想要显示一个模态时,触发一个动作来描述你想要看到的模态:
调度一个动作来显示模态
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 */
})
太棒了!现在,当您分派一个动作时,请声明。模态将更新到包含当前可见的模态窗口的信息。
编写根模态组件
在组件层次结构的根,添加一个连接到Redux存储的<ModalRoot>组件。它将听取国家的意见。并显示适当的模态组件,从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。并呈现相应的组件,如DeletePostModal或ConfirmLogoutModal。每个模态都是一个组件!
编写特定的模态组件
这里没有一般的规则。它们只是React组件,可以分派动作,从存储状态中读取一些东西,只是碰巧是模态。
例如,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。
提取一个表示组件
为每个“特定的”模态复制粘贴相同的布局逻辑会很尴尬。但是你有组件,对吧?因此,您可以提取一个presentational <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时隐藏它们。
我不建议您如何实现它,而是建议您不要自己实现它。考虑到可访问性,很难做出正确的选择。
相反,我建议你使用现成的模态组件,比如react-modal。它是完全可定制的,你可以把任何你想要的东西放在里面,但它正确地处理可访问性,以便盲人仍然可以使用你的模式。
你甚至可以在你自己的<Modal>中包装react-modal,它接受特定于你的应用程序的道具,并生成子按钮或其他内容。这都只是组成部分!
其他方法
做这件事的方法不止一种。
有些人不喜欢这种方法的冗长,他们更喜欢有一个<Modal>组件,他们可以使用一种称为“门户”的技术在组件中呈现该组件。门户可以让你在你的组件中呈现一个组件,而实际上它会在DOM中预先确定的位置呈现,这对于模态非常方便。
事实上,我之前链接到的react-modal已经在内部做了,所以技术上你甚至不需要从顶部渲染它。我仍然发现将我想要显示的模态与显示它的组件解耦是很好的,但你也可以直接从组件中使用react-modal,并跳过上面我所写的大部分内容。
我鼓励你考虑这两种方法,尝试它们,然后选择最适合你的应用和团队的方法。