Введение
Возможность повторного использования кода — отличная вещь. Я столкнулся с ситуацией, когда мне нужно создать четыре разных типа всплывающих окон для приложения.
Вместо того чтобы писать четыре отдельных компонента, можно было бы использовать один гибридный компонент.
Созданное мной глобальное всплывающее окно использует некоторые проверки состояния для отрисовки отдельных элементов пользовательского интерфейса. Я использовал recoil для управления состоянием, чтобы различные состояния компонента можно было контролировать глобально. Если обернуть аспект управления в хук, его будет легко использовать из различных частей приложения.
В этом посте я использовал Next.js для демонстрации.
Структура папки
Состояния
Каждая единица состояния (атом) хранится в отдельных файлах, и они обслуживаются из файла index.js в src/recoil/popup
.
Есть состояния, включенные для
- открыть или закрыть всплывающее окно,
- добавить заголовок,
- включения или отключения кнопки закрытия,
- отображения содержимого с помощью пользовательского компонента,
- включения или отключения изображений, а также
- передавать обработчики событий кнопкам.
Также предусмотрена возможность сброса состояния всплывающего окна на значение по умолчанию.
src/recoil/popup/index.js
import popupOpenOrClose from './popupOpenOrClose'
import popupHeading from './popupHeading'
import popupContentCloseMark from './popupContentCloseMark'
import popupCustomContent from './popupCustomContent'
import popupThumbsUp from './popupThumbsUp'
import popupThumbDown from './popupThumbDown'
import popupOkButton from './popupOkButton'
import popupCancelButton from './popupCancelButton'
import popupYesButton from './popupYesButton'
import useResetPopupState from './resetStates'
export {
popupOpenOrClose,
popupHeading,
popupContentCloseMark,
popupCustomContent,
popupThumbsUp,
popupThumbDown,
popupOkButton,
popupCancelButton,
popupYesButton,
useResetPopupState
}
src/recoil/popup/popupCancelButton.js
import { atom } from "recoil"
const popupCancelButton = atom({
key: "popupCancelButton",
default: false
})
export default popupCancelButton
В атоме popupCancelButton хранится функция, которая должна быть выполнена при событии onClick кнопки отмены.
src/recoil/popup/popupContentCloseMark.js
import { atom } from "recoil"
const popupContentCloseMark = atom({
key: "popupContentCloseMark",
default: false
})
export default popupContentCloseMark
Атом popupContentCloseMark хранит состояние того, должна ли во всплывающем окне присутствовать или отсутствовать кнопка закрытия.
src/recoil/popup/popupCustomContent.js
import { atom } from "recoil"
const popupCustomContent = atom({
key: "popupCustomContent",
default: <></>
})
export default popupCustomContent
Атом popupCustomContent хранит компонент пользовательского содержимого, который должен присутствовать или отсутствовать во всплывающем окне.
src/recoil/popup/popupHeading.js
import { atom } from "recoil"
const popupHeading = atom({
key: "popupHeading",
default: ''
})
export default popupHeading
Атом popupHeading хранит заголовок, который должен присутствовать или отсутствовать во всплывающем окне.
src/recoil/popup/popupOkButton.js
import { atom } from "recoil"
const popupOkButton = atom({
key: "popupOkButton",
default: false
})
export default popupOkButton
Атом popupOkButton хранит функцию, которая должна быть выполнена при событии onClick кнопки Ok.
src/recoil/popup/popupOpenOrClose.js
import { atom } from "recoil"
const popupOpenOrClose = atom({
key: "popupOpenOrClose",
default: false
})
export default popupOpenOrClose
Атом popupOpenOrClose хранит состояние того, должно ли всплывающее окно присутствовать или нет.
src/recoil/popup/popupThumbDown.js
import { atom } from "recoil"
const popupThumbDown = atom({
key: "popupThumbDown",
default: false
})
export default popupThumbDown
Атом popupThumbDown хранит состояние того, должно ли во всплывающем окне присутствовать или отсутствовать уменьшенное изображение.
src/recoil/popup/popupThumbsUp.js
import { atom } from "recoil"
const popupThumbsUp = atom({
key: "popupThumbsUp",
default: false
})
export default popupThumbsUp
Атом popupThumbsUp хранит информацию о том, должно ли во всплывающем окне присутствовать или отсутствовать изображение большого пальца вверх.
src/recoil/popup/popupYesButton.js
import { atom } from "recoil"
const popupYesButton = atom({
key: "popupYesButton",
default: false
})
export default popupYesButton
Атом popupYesButton хранит функцию, которая должна быть выполнена при событии onClick кнопки Yes.
src/recoil/popup/resetStates.js
import { useResetRecoilState } from "recoil";
import {
popupOpenOrClose,
popupHeading,
popupContentCloseMark,
popupCustomContent,
popupThumbsUp,
popupThumbDown,
popupOkButton,
popupCancelButton,
popupYesButton,
} from '../../recoil/popup'
const useResetPopupState = () => {
// reset state references
const resetOpenOrClosePopup = useResetRecoilState(popupOpenOrClose);
const resetPopupHeading = useResetRecoilState(popupHeading);
const resetContentCloseMarkPopup = useResetRecoilState(popupContentCloseMark);
const resetCustomContentPopup = useResetRecoilState(popupCustomContent);
const resetThumbsUpPopup = useResetRecoilState(popupThumbsUp);
const resetThumbDownPopup = useResetRecoilState(popupThumbDown);
const resetOkButtonPopup = useResetRecoilState(popupOkButton);
const resetCancelButtonPopup = useResetRecoilState(popupCancelButton);
const resetYesButtonPopup = useResetRecoilState(popupYesButton);
const resetState=()=>{
//reset to default
resetOpenOrClosePopup()
resetPopupHeading()
resetContentCloseMarkPopup()
resetCustomContentPopup()
resetThumbsUpPopup()
resetThumbDownPopup()
resetOkButtonPopup()
resetCancelButtonPopup()
resetYesButtonPopup()
}
return [resetState]
}
export default useResetPopupState
UseResetPopupState — это хук, используемый для сброса состояний, связанных с всплывающим окном. Он возвращает resetState
, вызывая resetState, мы можем вернуть состояния всплывающего окна к их значениям по умолчанию.
Пользовательский хук
src/hooks/usePopUp.js
import { useSetRecoilState } from "recoil";
import {
popupOpenOrClose,
popupHeading,
popupContentCloseMark,
popupCustomContent,
popupThumbsUp,
popupThumbDown,
popupOkButton,
popupCancelButton,
popupYesButton,
useResetPopupState
} from '../recoil/popup'
export const usePopUp = () => {
//states
const setOpenOrClosePopup = useSetRecoilState(popupOpenOrClose);
const setPopupHeading = useSetRecoilState(popupHeading);
const setContentCloseMarkPopup = useSetRecoilState(popupContentCloseMark);
const setcustomContentPopup = useSetRecoilState(popupCustomContent);
const setThumbsUpPopup = useSetRecoilState(popupThumbsUp);
const setThumbDownPopup = useSetRecoilState(popupThumbDown);
const setOkButtonPopup = useSetRecoilState(popupOkButton);
const setCancelButtonPopup = useSetRecoilState(popupCancelButton);
const setYesButtonPopup = useSetRecoilState(popupYesButton);
// hook
const [resetPopUp] = useResetPopupState()
// showPopup implmentation
const showPopup = (
{
heading,
showClose,
customContent,
ThumbUp,
ThumbDown,
onOkPressed,
onCancelpressed,
onYesPressed,
}) => {
resetPopUp()
setOpenOrClosePopup(true)
setPopupHeading(heading)
setContentCloseMarkPopup(showClose)
setcustomContentPopup(customContent)
setThumbsUpPopup(ThumbUp)
setThumbDownPopup(ThumbDown)
if (onOkPressed) {
setOkButtonPopup({ onClick: onOkPressed })
}
if (onCancelpressed) {
setCancelButtonPopup({ onClick: onCancelpressed })
}
if (onYesPressed) {
setYesButtonPopup({ onClick: onYesPressed })
}
}
// hidePopup implmentation
const hidePopup = () => {
resetPopUp()
}
return [showPopup, hidePopup]
}
Хук usePopUp()
возвращает [showPopup,hidePopup]
.
- Функция showPopup() принимает объект в качестве аргумента, который используется для установки свойств, а также различных обработчиков событий для всплывающего окна.
- Функция hidePopup() скрывает всплывающее окно и устанавливает его состояние в значение по умолчанию.
Компонент всплывающего окна
src/components/globalPopup/index.js
import React, { useEffect } from 'react'
import { useRecoilState, useRecoilValue } from "recoil";
import {
popupOpenOrClose,
popupHeading,
popupContentCloseMark,
popupCustomContent,
popupThumbsUp,
popupThumbDown,
popupOkButton,
popupCancelButton,
popupYesButton,
useResetPopupState
} from '../../recoil/popup'
import closeButton from '../../images/closeButton.png'
import thumbsUpImg from '../../images/thumbsUp.jpeg'
import innerClose from '../../images/innerClose.png'
import thumbsDownImg from '../../images/thumbsDown.jpeg'
import Image from 'next/image'
import styles from './globalPopup.module.css'
const GlobalPopup = () => {
// states
const [openOrClosePopup, setOpenOrClosePopup] = useRecoilState(popupOpenOrClose);
// values
const headingPopup = useRecoilValue(popupHeading);
const contentCloseMarkPopup = useRecoilValue(popupContentCloseMark);
const customContent = useRecoilValue(popupCustomContent);
const thumbsUpPopup = useRecoilValue(popupThumbsUp);
const thumbDownPopup = useRecoilValue(popupThumbDown);
const okButtonPopup = useRecoilValue(popupOkButton);
const cancelButtonPopup = useRecoilValue(popupCancelButton);
const yesButtonPopup = useRecoilValue(popupYesButton);
// hook
const[resetPopUp]=useResetPopupState()
// handling background scroll of body
useEffect(() => {
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = 'visible'
//reset to default values
resetPopUp()
}
}, [])
//event handlers
const toggleOpenOrClosePopup = () => {
setOpenOrClosePopup((prev) => { return !prev })
}
return (
openOrClosePopup && (
<div className={styles.popupWrap}>
<div className={styles.closeButtonWrap}>
{contentCloseMarkPopup && (
<div
className={styles.closeButton}
onClick={toggleOpenOrClosePopup}
>
<Image src={closeButton} layout="fill" />
</div>
)}
</div>
<div className={styles.boxBig}>
<div className={styles.boxSmall}>
{contentCloseMarkPopup && (
< div className={styles.innerCloseWrap} onClick={toggleOpenOrClosePopup}>
<Image src={innerClose} width="20" height="20" />
</div>
)}
{thumbsUpPopup && (
<div className={styles.popUpMainImage}>
<Image src={thumbsUpImg} layout="fill" />
</div>
)}
{thumbDownPopup && (
<div className={styles.popUpMainImage}>
<Image src={thumbsDownImg} layout="fill" />
</div>
)}
<div className={styles.newHeading}>
<div className={styles.newHeadingInner}>
<h1> {headingPopup}
</h1>
</div>
</div>
<p className={styles.prompt}>
{customContent}
</p>
{(okButtonPopup || yesButtonPopup || cancelButtonPopup) && (
<div className={styles.actionButtions}>
{okButtonPopup && <div className={styles.actionButtionWrap} onClick={okButtonPopup.onClick}>
<button className={styles.buttonSmall} >Ok</button>
</div>
}
{yesButtonPopup && <div className={styles.actionButtionWrap} onClick={yesButtonPopup.onClick}>
<button className={styles.buttonSmall} >Yes</button>
</div>}
{cancelButtonPopup && <div className={styles.actionButtionWrap} onClick={cancelButtonPopup.onClick}>
<button className={styles.buttonSmall} >Cancel</button>
</div>}
</div>
)}
</div>
</div>
</div >)
)
}
export default GlobalPopup
GlobalPopup
использует состояния, которые доступны глобально для рендеринга пользовательского интерфейса. Код внутри блока useEffect
предотвращает прокрутку тела, когда всплывающее окно активно, и делает видимым переполнение, когда всплывающее окно неактивно.
CSS всплывающего окна
src/components/globalPopup/globalPopup.module.css
.popupWrap {
position: fixed;
height: 100vh;
width: 100vw;
background-color: rgba(0, 0, 0, 0.1);
background-size: cover;
top: 0;
left: 0;
z-index: 99;
}
.closeButtonWrap {
position: absolute;
top: 10%;
right: 10%;
display: flex;
justify-content: flex-end;
cursor: pointer;
}
.closeButton {
position: relative;
width: 30px;
height: 30px;
}
.boxBig {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.boxSmall {
background: #fff;
border-radius: 16px;
border-top: 6px solid #5ddfb6;
padding: 40px 65px;
display: flex;
flex-direction: column;
align-items: center;
}
.actionButtions {
padding: 15px 0px;
display: flex;
flex-direction: row;
justify-content: space-around;
}
.actionButtionWrap {
margin: 0px 5px;
}
.actionButtionWrap button {
cursor: pointer;
}
.popUpMainImage {
position: relative;
width: 100px;
aspect-ratio: 1/1;
}
.specificEmail {
color: #9e8959;
}
.newHeading {
display: flex;
flex-direction: column;
width: 100%;
justify-content: center;
align-items: center;
}
.newHeadingInner {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 10px;
}
.newHeading h1 {
margin-bottom: 10px;
font-size: 30px;
font-weight: 500;
color: #000000;
text-align: center;
}
.prompt {
font-size: 16px;
line-height: 150%;
margin-top: 10px;
text-align: center;
font-weight: 400;
color: rgba(0, 0, 0, 0.68);
}
.prompt span {
color: #1cd7b8;
}
.innerCloseWrap {
display: none;
}
.buttonSmall {
width: 74px;
height: 27px;
font-family: "Poppins";
font-size: 0.8rem;
line-height: 24px;
background: #58c0ce;
border-radius: 3px;
border-color: #525251;
box-shadow: 0 0 0 0;
color: white;
border: none;
padding: 0;
padding: 2px 8px 2px 8px;
transition: 0.4s;
}
.buttonSmall:hover {
background: #5ddfb6;
}
@media screen and (max-width: 560px) {
.prompt {
font-size: 14px;
margin-top: 10px;
}
.closeButtonWrap {
display: none;
}
.boxBig {
width: 80%;
}
.boxSmall {
width: 100%;
}
.innerCloseWrap {
display: block;
position: absolute;
width: 30px;
right: 0px;
top: 15px;
}
.popUpMainImage {
position: relative;
width: 70px;
aspect-ratio: 1/1;
}
.newHeading h1 {
margin-bottom: 5px;
font-size: 25px;
font-weight: 500;
color: #000000;
}
.prompt {
margin-top: 10px;
}
}
@media screen and (max-width: 425px) {
.boxSmall {
padding: 40px 50px;
}
}
Это CSS, используемый для всплывающего окна. Всплывающее окно также является мобильно отзывчивым.
Включить компонент
src/pages/_app.js
import { RecoilRoot } from 'recoil'
import GlobalPopup from '../components/globalPopup'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return (
<RecoilRoot>
<>
<Component {...pageProps} />
<GlobalPopup/>
</>
</RecoilRoot>)
}
export default MyApp
Чтобы сделать всплывающее окно доступным как глобальный объект в приложении. Оно включается как компонент в _app.js
. Компонент оборачивается контекстом, предоставляемым RecoilRoot
, чтобы обеспечить доступность состояний атомов.
Протестируйте всплывающее окно
src/pages/testPopUps/index.js
import React from 'react'
import { usePopUp } from '../../hooks/usePopUp';
const TestPopup = () => {
const [showPopup, hidePopup] = usePopUp()
// showPopup - use it for diplaying popups with specified options
// hidePopup - use it for hiding the popup (as well as to reset to default state)
return (
<div>
<button onClick={() => {
showPopup({
ThumbUp: true,
heading: 'Success !',
showClose: true,
customContent: <>The process is <span>Successful</span></>
})
}}>
Activate Popup 1
</button>
<button onClick={() => {
showPopup({
showClose: true,
ThumbDown: true,
heading: 'Error !',
customContent: <>An Error Occured.</>
})
}}>
Activate Popup 2
</button>
<button onClick={() => {
showPopup({
customContent: <>An account with email <span>John@gmail.com</span> already exist</>,
onCancelpressed: () => {
console.log("onCancelpressed");
hidePopup()
},
onYesPressed: () => {
console.log("onYesPressed");
hidePopup()
}
})
}}>
Activate Popup 3
</button>
<button onClick={() => {
showPopup({
customContent: <>An account with email <span>John@gmail.com</span> already exist</>,
onOkPressed: () => {
console.log("onOkPressed");
hidePopup()
}
})
}}>
Activate Popup 4
</button>
</div >
)
}
export default TestPopup
Для тестирования всплывающего окна мы создали страницу, на которой есть четыре кнопки, и каждая из этих кнопок представляет всплывающее окно в различной спецификации.
Используя showPopup()
мы можем настроить всплывающее окно с различными опциями.
Вывод
Таким образом, один и тот же компонент можно использовать повторно, отображать условно и управлять им из любой точки приложения.
Спасибо.