Подход, который я предлагаю, немного многословен, но я нашел его достаточно хорошо масштабируемым для сложных приложений. Если вы хотите показать модальный режим, запустите действие, описывающее, какой модальный режим вы хотели бы видеть:
Отправка действия, чтобы показать модальное
this.props.dispatch({
type: 'SHOW_MODAL',
modalType: 'DELETE_POST',
modalProps: {
postId: 42
}
})
(Конечно, строки могут быть константами; для простоты я использую встроенные строки.)
Написание редуктора для управления модальным состоянием
Затем убедитесь, что у вас есть редуктор, который просто принимает эти значения:
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
из state.modal
которого он подключен, и отображает соответствующий компонент, такой как 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
когда необходимо скрыть себя.
Извлечение презентационного компонента
Было бы неудобно копировать и вставлять одну и ту же логику компоновки для каждого «конкретного» модала. Но у вас есть компоненты, верно? Таким образом, вы можете извлечь презентационный <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>
который принимает реквизиты, специфичные для ваших приложений и генерирует дочерние кнопки или другой контент. Это все просто компоненты!
Другие подходы
Существует несколько способов сделать это.
Некоторым людям не нравится многословность этого подхода, и они предпочитают иметь <Modal>
компонент, который они могут визуализировать прямо внутри своих компонентов с помощью техники, называемой «порталы». Порталы позволяют вам визуализировать компонент внутри вашего, в то время как фактически он будет отображаться в предопределенном месте в DOM, что очень удобно для модальных объектов.
На самом деле, react-modal
я уже ссылался на ранее сделанное внутренне, так что технически вам даже не нужно рендерить его сверху. Мне все еще приятно отделить модал, который я хочу показать, от компонента, который его показывает, но вы также можете использовать его react-modal
непосредственно из своих компонентов и пропустить большую часть того, что я написал выше.
Я рекомендую вам рассмотреть оба подхода, поэкспериментировать с ними и выбрать то, что, по вашему мнению, лучше всего подходит для вашего приложения и для вашей команды.