Підхід, який я пропоную, є дещо багатослівним, але я виявив, що він досить масштабний у складних додатках. Коли ви хочете показати модальний режим, запустіть дію, описуючи яку модал ви хочете бачити:
Відправлення дії для показу модалу
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
буде оновлено, щоб включити інформацію про видиме в даний час модальне вікно.
Запис компонента Root 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
безпосередньо зі своїх компонентів і пропустити більшу частину написаного вище.
Я рекомендую вам розглянути обидва підходи, експериментувати з ними та вибрати те, що, як вам здається, найкраще підходить для вашої програми та для вашої команди.