Подход, который я предлагаю, немного многословен, но я обнаружил, что он довольно хорошо масштабируется в сложных приложениях. Если вы хотите показать модальное окно, запустите действие, описывающее какой модальный файл вы хотите видеть:
Отправка действия для отображения модального окна
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.
Вместо того, чтобы давать вам советы по реализации этого, я предлагаю вам просто не реализовывать это самостоятельно. Это сложно сделать, учитывая доступность.
Вместо этого я бы посоветовал вам использовать готовый доступный модальный компонент, такой как react-modal
. Он полностью настраиваемый, вы можете поместить в него все, что захотите, но он правильно обрабатывает доступность, чтобы слепые люди по-прежнему могли использовать ваш модальный.
Вы даже можете заключить react-modal
в свой собственный <Modal>
, который принимает свойства, специфичные для ваших приложений, и генерирует дочерние кнопки или другой контент. Это всего лишь компоненты!
Другие подходы
Есть несколько способов сделать это.
Некоторым людям не нравится многословие этого подхода, и они предпочитают иметь <Modal>
компонент, который они могут отображать прямо внутри своих компонентов с помощью техники, называемой «порталами». Порталы позволяют визуализировать компонент внутри вашего, в то время как на самом деле он будет отображаться в заранее определенном месте в DOM, что очень удобно для модальных окон.
Фактически, react-modal
, на который я ссылался ранее, уже делает это внутренне, поэтому технически вам даже не нужно рендерить это сверху. Мне по-прежнему приятно отделить модальное окно, которое я хочу показать, от компонента, показывающего его, но вы также можете использовать react-modal
прямо из ваших компонентов и пропустить большую часть того, что я написал выше.
Я призываю вас рассмотреть оба подхода, поэкспериментировать с ними и выбрать то, что, по вашему мнению, лучше всего подходит для вашего приложения и для вашей команды.
person
Dan Abramov
schedule
26.02.2016