Праздник середины осени немного скучен, поэтому я пишу сообщение в блоге, чтобы согреть свое сердце, и желаю всем счастливого праздника середины осени~ 🙃
Далее я покажу вам шаг за шагом, как реализовать базовый компонент модального всплывающего окна и инкапсулировать простой компонент анимации.
1. Реализация модальной составляющей;
1. Строительство окружающей среды
Мы используем команду create-react-app для быстрого создания среды разработки:
create-react-app modal
После завершения установки следуйте инструкциям, чтобы запустить проект, а затемsrc
новый каталогmodal
каталог, при созданииmodal.jsx modal.css
два файла
modal.jsx
Содержание следующее:
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
render() {
return <div className="modal">
这是一个modal组件
</div>
}
}
export default Modal;
Вернитесь в корневой каталог, откройте App.js и замените содержимое следующим:
import Modal from './modal/modal';
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
return <div className="app">
<Modal></Modal>
</div>
}
}
export default App;
После выполнения вышеуказанных шагов наш браузер отобразит следующее изображение:
2. Модальный стиль идеален
Прежде чем писать, давайте вспомним, какие элементы являются модальными компонентами, которые мы обычно используем, область заголовка, область содержимого, область управления и маску;
modal.jsx
Содержимое изменено следующим образом:
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
render() {
return <div className="modal-wrapper">
<div className="modal">
<div className="modal-title">这是modal标题</div>
<div className="modal-content">这是modal内容</div>
<div className="modal-operator">
<button className="modal-operator-close">取消</button>
<button className="modal-operator-confirm">确认</button>
</div>
</div>
<div className="mask"></div>
</div>
}
}
export default Modal;
modal.css
Содержимое изменено следующим образом:
.modal {
position: fixed;
width: 300px;
height: 200px;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
border-radius: 5px;
background: #fff;
overflow: hidden;
z-index: 9999;
box-shadow: inset 0 0 1px 0 #000;
}
.modal-title {
width: 100%;
height: 50px;
line-height: 50px;
padding: 0 10px;
}
.modal-content {
width: 100%;
height: 100px;
padding: 0 10px;
}
.modal-operator {
width: 100%;
height: 50px;
}
.modal-operator-close, .modal-operator-confirm {
width: 50%;
border: none;
outline: none;
height: 50px;
line-height: 50px;
opacity: 1;
color: #fff;
background: rgb(247, 32, 32);
cursor: pointer;
}
.modal-operator-close:active, .modal-operator-confirm:active {
opacity: .6;
transition: opacity .3s;
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #000;
opacity: .6;
z-index: 9998;
}
После завершения модификации наш браузер отобразит следующее изображение:
3. развитие модальной функции
На этом наши приготовления завершены.Далее мы будем реализовывать модальную функцию.Напомним, когда мы будем использовать модальную составляющую, какие основные функции будут?
- в состоянии пройти
visible
контрольmodal
явные и неявные; -
title
,content
Содержимое дисплея можно настроить; - Нажмите Отмена, чтобы закрыть
modal
, который также вызываетonClose
Обратный вызов, нажав OK, вызовет обратный вызов с именемconfirm
обратный вызов и закрытьmodal
, нажмите на маскуmask
закрытиеmodal
; - Поле анимации может включать/выключать анимацию;
3.1. Добавитьvisible
Отображение и скрытие управления полем
modal.jsx
изменить, как показано ниже:
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
}
render() {
// 通过父组件传递的visile控制显隐
const { visible } = this.props;
return visible && <div className="modal-wrapper">
<div className="modal">
<div className="modal-title">这是modal标题</div>
<div className="modal-content">这是modal内容</div>
<div className="modal-operator">
<button className="modal-operator-close">取消</button>
<button className="modal-operator-confirm">确认</button>
</div>
</div>
<div className="mask"></div>
</div>
}
}
export default Modal;
App.js
изменить, как показано ниже:
import Modal from './modal/modal';
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
// 这里绑定this因为类中的方法不会自动绑定指向当前示例,我们需要手动绑定,不然方法中的this将是undefined,这是其中一种绑定的方法,
// 第二种方法是使用箭头函数的方法,如:showModal = () => {}
// 第三种方法是调用的时候绑定,如:this.showModal.bind(this)
this.showModal = this.showModal.bind(this)
this.state = {
visible: false
}
}
showModal() {
this.setState({ visible: true })
}
render() {
const { visible } = this.state
return <div className="app">
<button onClick={this.showModal}>click here</button>
<Modal visible={visible}></Modal>
</div>
}
}
export default App;
Выше мы передаем родительский компонентApp.js
видимое состояние в , переданоmodal
компоненты, затем пройтиbutton
Событие click для управления значением visible для достижения контроляmodal
Эффект отображения и скрытия компонента
Эффект отсутствия нажатия кнопки следующий:
После нажатия кнопки эффект следующий:
3.2. title
иcontent
Настройка контента
modal.jsx
изменить, как показано ниже:
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
}
render() {
const { visible, title, children } = this.props;
return visible && <div className="modal-wrapper">
<div className="modal">
{/* 这里使用父组件的title*/}
<div className="modal-title">{title}</div>
{/* 这里的content使用父组件的children*/}
<div className="modal-content">{children}</div>
<div className="modal-operator">
<button className="modal-operator-close">取消</button>
<button className="modal-operator-confirm">确认</button>
</div>
</div>
<div className="mask"></div>
</div>
}
}
export default Modal;
App.js
изменить, как показано ниже:
import Modal from './modal/modal';
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
this.showModal = this.showModal.bind(this)
this.state = {
visible: false
}
}
showModal() {
this.setState({ visible: true })
}
render() {
const { visible } = this.state
return <div className="app">
<button onClick={this.showModal}>click here</button>
<Modal
visible={visible}
title="这是自定义title"
>
这是自定义content
</Modal>
</div>
}
}
export default App;
Затем мы нажимаем кнопку на странице, и результат отображается следующим образом:
3.3 Добавлены кнопки отмены и подтверждения и функция щелчка по маске
Подумай, прежде чем писать: нам нужно нажать кнопку «Отмена», чтобы закрыть
modal
, то нам нужноmodal
поддерживать состояние вmodal
Это кажется выполнимым, но давайте еще раз подумаем, мы передали родительский компонент перед собойvisible
контрольmodal
Это противоречие? Если это не работает, давайте внесем изменения, если состояние родительского компонента изменится, то мы только обновим это состояние.modal
Нажмите «Отмена» в середине, мы только обновим это состояние и, наконец, используем это значение состояния для управленияmodal
явность и сокрытие; что касаетсяonClose
Мы можем вызвать функцию ловушки перед обновлением состояния, а щелчок кнопки подтверждения аналогичен отмене.
modal.jsx
изменить, как показано ниже:
import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false
}
}
// 首次渲染使用父组件的状态更新modal中的visible状态,只调用一次
componentDidMount() {
this.setState({ visible: this.props.visible })
}
// 每次接收props就根据父组件的状态更新modal中的visible状态,首次渲染不会调用
componentWillReceiveProps(props) {
this.setState({ visible: props.visible })
}
// 点击取消更新modal中的visible状态
closeModal() {
console.log('大家好,我叫取消,听说你们想点我?傲娇脸👸')
const { onClose } = this.props
onClose && onClose()
this.setState({ visible: false })
}
confirm() {
console.log('大家好,我叫确认,楼上的取消是我儿子,脑子有点那个~')
const { confirm } = this.props
confirm && confirm()
this.setState({ visible: false })
}
maskClick() {
console.log('大家好,我是蒙层,我被点击了')
this.setState({ visible: false})
}
render() {
// 使用modal中维护的visible状态来控制显隐
const { visible } = this.state;
const { title, children } = this.props;
return visible && <div className="modal-wrapper">
<div className="modal">
<div className="modal-title">{title}</div>
<div className="modal-content">{children}</div>
<div className="modal-operator">
<button
onClick={this.closeModal}
className="modal-operator-close"
>取消</button>
<button
onClick={this.confirm}
className="modal-operator-confirm"
>确认</button>
</div>
</div>
<div
className="mask"
onClick={this.maskClick}
></div>
</div>
}
}
export default Modal;
App.js
изменить, как показано ниже:
import Modal from './modal/modal';
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.showModal = this.showModal.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false
}
}
showModal() {
this.setState({ visible: true })
}
closeModal() {
console.log('我是onClose回调')
}
confirm() {
console.log('我是confirm回调')
}
render() {
const { visible } = this.state
return <div className="app">
<button onClick={this.showModal}>click here</button>
<Modal
visible={visible}
title="这是自定义title"
confirm={this.confirm}
onClose={this.closeModal}
>
这是自定义content
</Modal>
</div>
}
}
export default App;
После сохранения мы нажимаем Cancel и Confirm соответственно в браузере, и консоль будет выглядеть так, как показано ниже:
4. модальная оптимизация
Вышеизложенное завершает базовоеmodal
компонента, но у нас остался вопрос, который сейчас вводитсяmodal
находится в классе с именемApp
элементы и некоторые широко используемые UI-фреймворкиmodal
компонент делает вbody
слоя, где бы вы его ни вводили, это предотвращаетmodal
Компоненту мешают стили родительского компонента.
Чтобы достичь этого эффекта, мы должны сначала понять функции, которые приходят с React:Portals
(портал). Эта функция была добавлена после версии 16 и до версии 16 с помощьюReactDOM
изunstable_renderSubtreeIntoContainer
Обработка метода, этот метод может преобразовать элемент в указанный элемент иReactDOM.render
Отличие методов в том, что контекст текущего компонента может быть сохраненcontext
,react-redux
основан наcontext
Связь между компонентами осуществляется, поэтому, если вы используетеReactDOM.render
Рендеринг приведет к потере контекста, что приведет кcontext
Фреймворки, реализующие межкомпонентное взаимодействие, терпят неудачу.
4.1. ReactDOM.unstable_renderSubtreeIntoContainer
использование
ReactDOM.unstable_renderSubtreeIntoContainer(
parentComponent, // 用来指定上下文
element, // 要渲染的元素
containerNode, // 渲染到指定的dom中
callback // 回调
);
Далее, чтобы использовать его в нашем проекте,src
новый каталогoldPortal
директорию и создать в ней новуюoldPortal.jsx
,oldPortal.jsx
Содержание следующее:
import React from 'react';
import ReactDOM from 'react-dom';
class OldPortal extends React.Component {
constructor(props) {
super(props)
}
// 初始化时根据visible属性来判断是否渲染
componentDidMount() {
const { visible } = this.props
if (visible) {
this.renderPortal(this.props);
}
}
// 每次接受到props进行渲染与卸载操作
componentWillReceiveProps(props) {
if (props.visible) {
this.renderPortal(props)
} else {
this.closePortal()
}
}
// 渲染
renderPortal(props) {
if (!this.node) {
// 防止多次创建node
this.node = document.createElement('div');
}
// 将当前node添加到body中
document.body.appendChild(this.node);
ReactDOM.unstable_renderSubtreeIntoContainer(
this, // 上下文指定当前的实例
props.children, // 渲染的元素为当前的children
this.node, // 将元素渲染到我们新建的node中,这里我们不使用第四个参数回调.
);
}
// 卸载
closePortal() {
if (this.node) {
// 卸载元素中的组件
ReactDOM.unmountComponentAtNode(this.node)
// 移除元素
document.body.removeChild(this.node)
}
}
render() {
return null;
}
}
export default OldPortal
После сохранения мыmodal.jsx
использовать его в:
import React, { Component } from 'react';
import OldPortal from '../oldPortal/oldPortal';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false
}
}
componentDidMount() {
this.setState({ visible: this.props.visible })
}
componentWillReceiveProps(props) {
this.setState({ visible: props.visible })
}
closeModal() {
console.log('大家好,我叫取消,听说你们想点我?傲娇脸👸')
const { onClose } = this.props
onClose && onClose()
this.setState({ visible: false })
}
confirm() {
console.log('大家好,我叫确认,楼上的取消是我儿子,脑子有点那个~')
const { confirm } = this.props
confirm && confirm()
this.setState({ visible: false })
}
maskClick() {
console.log('大家好,我是蒙层,我被点击了')
this.setState({ visible: false })
}
render() {
const { visible } = this.state;
const { title, children } = this.props;
return <OldPortal visible={visible}>
<div className="modal-wrapper">
<div className="modal">
<div className="modal-title">{title}</div>
<div className="modal-content">{children}</div>
<div className="modal-operator">
<button
onClick={this.closeModal}
className="modal-operator-close"
>取消</button>
<button
onClick={this.confirm}
className="modal-operator-confirm"
>确认</button>
</div>
</div>
<div
className="mask"
onClick={this.maskClick}
></div>
</div>
</OldPortal>
}
}
export default Modal;
Как видите, мы толькоmodal
серединаreturn
Содержимое внешнего оборачивает слойOldPortal
компонент, который затем будет контролировать состояние отображения и скрытияvisible
перешел кOldPortal
компоненты, поOldPortal
фактически контролироватьmodal
Отображать и скрывать, затем нажимаем кнопку на странице, одновременно открываем консоль и находимmodal
Как мы и думали, кровать была доставленаbody
Этаж:
4.2.16 версияPortal
использовать
В версии 16,react-dom
изначально предоставляет методReactDOM.createPortal()
, используемый для реализации функции портала:
ReactDOM.createPortal(
child, // 要渲染的元素
container // 指定渲染的父元素
)
соотношение параметровunstable_renderSubtreeIntoContainer
Сократили до двух, а потом используем в проекте.
существуетsrc
новый каталогnewPortal
каталог, в котором будет создан новыйnewPortal.jsx
,newPortal.jsx
Содержание следующее:
import React from 'react';
import ReactDOM from 'react-dom';
class NewPortal extends React.Component {
constructor(props) {
super(props)
// 初始化创建渲染的父元素并添加到body下
this.node = document.createElement('div');
document.body.appendChild(this.node);
}
render() {
const { visible, children } = this.props;
// 直接通过显隐表示
return visible && ReactDOM.createPortal(
children,
this.node,
);
}
}
export default NewPortal
Вы можете четко видеть сравнение контентаunstable_renderSubtreeIntoContainer
Реализация сильно упрощается, тогда имеемmodal.jsx
используется в:
import React, { Component } from 'react';
import NewPortal from '../newPortal/newPortal';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false
}
}
componentDidMount() {
this.setState({ visible: this.props.visible })
}
componentWillReceiveProps(props) {
this.setState({ visible: props.visible })
}
closeModal() {
console.log('大家好,我叫取消,听说你们想点我?傲娇脸👸')
const { onClose } = this.props
onClose && onClose()
this.setState({ visible: false })
}
confirm() {
console.log('大家好,我叫确认,楼上的取消是我儿子,脑子有点那个~')
const { confirm } = this.props
confirm && confirm()
this.setState({ visible: false })
}
maskClick() {
console.log('大家好,我是蒙层,我被点击了')
this.setState({ visible: false })
}
render() {
const { visible } = this.state;
const { title, children } = this.props;
return <NewPortal visible={visible}>
<div className="modal-wrapper">
<div className="modal">
<div className="modal-title">{title}</div>
<div className="modal-content">{children}</div>
<div className="modal-operator">
<button
onClick={this.closeModal}
className="modal-operator-close"
>取消</button>
<button
onClick={this.confirm}
className="modal-operator-confirm"
>确认</button>
</div>
</div>
<div
className="mask"
onClick={this.maskClick}
></div>
</div>
</NewPortal>
}
}
export default Modal;
Используйте сOldPortal
То же самое, давайте взглянем на браузер, чтобы убедиться, что эффект такой, как мы думали:
Можно сказатьPortals
Это душа компонента класса всплывающего окна, вот правильныйPortals
Он используется только в качестве руководства для объяснения его основных функций и не углубляется в реализацию некоторых сложных общедоступных методов.Заинтересованные читатели могут искать соответствующие статьи для получения более подробных объяснений.
2. Реализация анимации входа и выхода
1. Добавьте анимацию
Начните с простого эффекта (используется тот же код, что и выше).NewPortal
компонентModal
компоненты),modal
Когда он появляется, он постепенно увеличивается, увеличивается до 1,1 раза и, наконец, уменьшается до 1 раза.При скрытии сначала увеличивается до 1,1 раза, затем уменьшается, пока не исчезнет.
Во-первых, обычное мышление: что мы контролируем, чтобы добиться эффекта увеличения и уменьшения масштаба? Как мы меняем процесс увеличения и уменьшения масштаба с мгновенного на постепенный? Когда мы начинаем увеличивать и уменьшать масштаб? Когда мы заканчиваем увеличение и уменьшение масштаба?
Увеличивайте и уменьшайте масштаб, когда мы проходимcss3
свойстваtransform scale
Для управления используйте эффект градиентаtransition
Чрезмерное кажется хорошим выбором, и время увеличения и уменьшения масштаба делится на шесть состояний: элементы начинают появляться, появляются, появляются и заканчиваются, начинают исчезать, исчезают, исчезают и заканчиваются, а затем мы определяем шесть состояния соответственно.scale
параметры, затем используйтеtransition
Чрезмерное, должно быть в состоянии добиться нужного нам эффекта:
Сноваmodal.css
Добавьте следующий код:
.modal-enter {
transform: scale(0);
}
.modal-enter-active {
transform: scale(1.1);
transition: all .2s linear;
}
.modal-enter-end {
transform: scale(1);
transition: all .1s linear;
}
.modal-leave {
transform: scale(1);
}
.modal-leave-active {
transform: scale(1.1);
transition: all .1s linear;
}
.modal-leave-end {
transform: scale(0);
transition: all .2s linear;
}
Шесть имен классов определяют шесть состояний появления и исчезновения и устанавливают соответствующие времена перехода.Затем мы добавляем соответствующие имена классов к элементам в разных процессах, чтобы управлять состоянием отображения элементов.
Перед тем, как писать логику, нам также нужно обратить внимание на то, что отображение и скрытие наших компонентов находится вNewPortal
Собственно управление в компоненте, но мы вModal
При добавлении анимации в компонент необходимо строго контролировать время показа и скрытия, например, анимация начнется сразу после рендеринга, а может быть скрыта после окончания анимации, что не подходит дляNewPortal
Элемент управления отображается и скрывается в компоненте.Некоторые читатели недоумевают, почему нельзя напрямуюNewPortal
А как насчет добавления анимации к компонентам?Конечно, ответ на этот вопрос положительный, ноNewPortal
Функция заключается в передаче, это не сложная анимация, мы хотим, чтобы она была чистой и не должна сочетаться с другими компонентами.
ИсправлятьnewPortal.jsx
Содержание следующее:
import React from 'react';
import ReactDOM from 'react-dom';
class NewPortal extends React.Component {
constructor(props) {
super(props)
this.node = document.createElement('div');
document.body.appendChild(this.node);
}
render() {
const { children } = this.props;
return ReactDOM.createPortal(
children,
this.node,
);
}
}
export default NewPortal
Исправлятьmodal.jsx
Содержание следующее:
import React, { Component } from 'react';
import NewPortal from '../newPortal/newPortal';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.leaveAnimate = this.leaveAnimate.bind(this)
this.enterAnimate = this.enterAnimate.bind(this)
this.state = {
visible: false,
classes: null,
}
}
componentDidMount() {
this.setState({ visible: this.props.visible })
}
componentWillReceiveProps(props) {
if (props.visible) {
// 接收到父组件的props时,如果是true则进行动画渲染
this.enterAnimate()
}
}
// 进入动画
enterAnimate() {
// 这里定义每种状态的类名,就是我们之前modal.css文件中添加的类
const enterClasses = 'modal-enter'
const enterActiveClasses = 'modal-enter-active'
const enterEndActiveClasses = 'modal-enter-end'
// 这里定义了每种状态的过度时间,对应着modal.css中对应类名下的transition属性的时间,这里的单位为毫秒
const enterTimeout = 0
const enterActiveTimeout = 200
const enterEndTimeout = 100
// 将显隐状态改为true,同时将classes改为enter状态的类名
this.setState({ visible: true, classes: enterClasses })
// 这里使用定时器,是因为定时器中的函数会被加入到事件队列,带到主线程任务进行完成才会被调用,相当于在元素渲染出来并且加上初始的类名后enterTimeout时间后开始执行.
// 因为开始状态并不需要过度,所以我们直接将之设置为0.
const enterActiveTimer = setTimeout(_ => {
this.setState({ classes: enterActiveClasses })
clearTimeout(enterActiveTimer)
}, enterTimeout)
const enterEndTimer = setTimeout(_ => {
this.setState({ classes: enterEndActiveClasses })
clearTimeout(enterEndTimer)
}, enterTimeout + enterActiveTimeout)
// 最后将类名置空,还原元素本来的状态
const initTimer = setTimeout(_ => {
this.setState({ classes: '' })
clearTimeout(initTimer)
}, enterTimeout + enterActiveTimeout + enterEndTimeout)
}
// 离开动画
leaveAnimate() {
const leaveClasses = 'modal-leave'
const leaveActiveClasses = 'modal-leave-active'
const leaveEndActiveClasses = 'modal-leave-end'
const leaveTimeout = 0
const leaveActiveTimeout = 100
const leaveEndTimeout = 200
// 初始元素已经存在,所以不需要改变显隐状态
this.setState({ classes: leaveClasses })
const leaveActiveTimer = setTimeout(_ => {
this.setState({ classes: leaveActiveClasses })
clearTimeout(leaveActiveTimer)
}, leaveTimeout)
const leaveEndTimer = setTimeout(_ => {
this.setState({ classes: leaveEndActiveClasses })
clearTimeout(leaveEndTimer)
}, leaveTimeout + leaveActiveTimeout)
// 最后将显隐状态改为false,同时将类名还原为初始状态
const initTimer = setTimeout(_ => {
this.setState({ visible: false, classes: '' })
clearTimeout(initTimer)
}, leaveTimeout + leaveActiveTimeout + leaveEndTimeout)
}
closeModal() {
console.log('大家好,我叫取消,听说你们想点我?傲娇脸👸')
const { onClose } = this.props
onClose && onClose()
// 点击取消后调用离开动画
this.leaveAnimate()
}
confirm() {
console.log('大家好,我叫确认,楼上的取消是我儿子,脑子有点那个~')
const { confirm } = this.props
confirm && confirm()
this.leaveAnimate()
}
maskClick() {
console.log('大家好,我是蒙层,我被点击了')
this.setState({ visible: false })
}
render() {
const { visible, classes } = this.state;
const { title, children } = this.props;
return <NewPortal>
<div className="modal-wrapper">
{
visible &&
<div className={`modal ${classes}`}>
<div className="modal-title">{title}</div>
<div className="modal-content">{children}</div>
<div className="modal-operator">
<button
onClick={this.closeModal}
className="modal-operator-close"
>取消</button>
<button
onClick={this.confirm}
className="modal-operator-confirm"
>确认</button>
</div>
</div>
}
{/* 这里暂时注释蒙层,防止干扰 */}
{/* <div
className="mask"
onClick={this.maskClick}
></div> */}
</div>
</NewPortal>
}
}
export default Modal;
Эффект следующий:
2. Инкапсуляция компонентов анимации
Эффект анимации достигается, но код весь вmodal.jsx
, это совсем не элегантно, и его нельзя использовать повторно, поэтому нам нужно подумать о том, чтобы абстрагировать его вTransition
компоненты.
Идея: Начнем с необходимых функций, чтобы рассмотреть, как инкапсулировать. Прежде всего, входящее явное и неявное значение состояния контролирует явность и скрытие элемента; дайте имя класса, которое может совпадать с соответствующими шестью именами классов состояний; вы можете настроить время перехода каждого состояния; вы можете контролировать, следует ли использовать анимацию;
существуетsrc
новый каталогtransition
каталог, создать файлtransition.jsx
, содержание следующее:
import React from 'react';
// 这里引入classnames处理类名的拼接
import classnames from 'classnames';
class Transition extends React.Component {
constructor(props) {
super(props)
this.getClasses = this.getClasses.bind(this)
this.enterAnimate = this.enterAnimate.bind(this)
this.leaveAnimate = this.leaveAnimate.bind(this)
this.appearAnimate = this.appearAnimate.bind(this)
this.cloneChildren = this.cloneChildren.bind(this)
this.state = {
visible: false,
classes: null,
}
}
// 过渡时间不传入默认为0
static defaultProps = {
animate: true,
visible: false,
transitionName: '',
appearTimeout: 0,
appearActiveTimeout: 0,
appearEndTimeout: 0,
enterTimeout: 0,
enterActiveTimeout: 0,
enterEndTimeout: 0,
leaveTimeout: 0,
leaveEndTimeout: 0,
leaveActiveTimeout: 0,
}
// 这里我们添加了首次渲染动画。只出现一次
componentWillMount() {
const { transitionName, animate, visible } = this.props;
if (!animate) {
this.setState({ visible })
return
}
this.appearAnimate(this.props, transitionName)
}
componentWillReceiveProps(props) {
const { transitionName, animate, visible } = props
if (!animate) {
this.setState({ visible })
return
}
if (!props.visible) {
this.leaveAnimate(props, transitionName)
} else {
this.enterAnimate(props, transitionName)
}
}
// 首次渲染的入场动画
appearAnimate(props, transitionName) {
const { visible, appearTimeout, appearActiveTimeout, appearEndTimeout } = props
const { initClasses, activeClasses, endClasses } = this.getClasses('appear', transitionName)
this.setState({ visible, classes: initClasses })
setTimeout(_ => {
this.setState({ classes: activeClasses })
}, appearTimeout)
setTimeout(_ => {
this.setState({ classes: endClasses })
}, appearActiveTimeout + appearTimeout)
setTimeout(_ => {
this.setState({ classes: '' })
}, appearEndTimeout + appearActiveTimeout + appearTimeout)
}
// 入场动画
enterAnimate(props, transitionName) {
const { visible, enterTimeout, enterActiveTimeout, enterEndTimeout } = props
const { initClasses, activeClasses, endClasses } = this.getClasses('enter', transitionName)
this.setState({ visible, classes: initClasses })
const enterTimer = setTimeout(_ => {
this.setState({ classes: activeClasses })
clearTimeout(enterTimer)
}, enterTimeout)
const enterActiveTimer = setTimeout(_ => {
this.setState({ classes: endClasses })
clearTimeout(enterActiveTimer)
}, enterActiveTimeout + enterTimeout)
const enterEndTimer = setTimeout(_ => {
this.setState({ classes: '' })
clearTimeout(enterEndTimer)
}, enterEndTimeout + enterActiveTimeout + enterTimeout)
}
// 出场动画
leaveAnimate(props, transitionName) {
const { visible, leaveTimeout, leaveActiveTimeout, leaveEndTimeout } = props
const { initClasses, activeClasses, endClasses } = this.getClasses('leave', transitionName)
this.setState({ classes: initClasses })
const leaveTimer = setTimeout(_ => {
this.setState({ classes: activeClasses })
clearTimeout(leaveTimer)
}, leaveTimeout)
const leaveActiveTimer = setTimeout(_ => {
this.setState({ classes: endClasses })
clearTimeout(leaveActiveTimer)
}, leaveActiveTimeout + leaveTimeout)
const leaveEndTimer = setTimeout(_ => {
this.setState({ visible, classes: '' })
clearTimeout(leaveEndTimer)
}, leaveEndTimeout + leaveActiveTimeout + leaveTimeout)
}
// 类名统一配置
getClasses(type, transitionName) {
const initClasses = classnames({
[`${transitionName}-appear`]: type === 'appear',
[`${transitionName}-enter`]: type === 'enter',
[`${transitionName}-leave`]: type === 'leave',
})
const activeClasses = classnames({
[`${transitionName}-appear-active`]: type === 'appear',
[`${transitionName}-enter-active`]: type === 'enter',
[`${transitionName}-leave-active`]: type === 'leave',
})
const endClasses = classnames({
[`${transitionName}-appear-end`]: type === 'appear',
[`${transitionName}-enter-end`]: type === 'enter',
[`${transitionName}-leave-end`]: type === 'leave',
})
return { initClasses, activeClasses, endClasses }
}
cloneChildren() {
const { classes } = this.state
const children = this.props.children
const className = children.props.className
// 通过React.cloneElement给子元素添加额外的props,
return React.cloneElement(
children,
{ className: `${className} ${classes}` }
)
}
render() {
const { visible } = this.state
return visible && this.cloneChildren()
}
}
export default Transition
modal.jsx
Содержимое изменено следующим образом:
import React, { Component } from 'react';
import NewPortal from '../newPortal/newPortal';
import Transition from '../transition/transition';
import './modal.css';
class Modal extends Component {
constructor(props) {
super(props)
this.confirm = this.confirm.bind(this)
this.maskClick = this.maskClick.bind(this)
this.closeModal = this.closeModal.bind(this)
this.state = {
visible: false,
}
}
componentDidMount() {
this.setState({ visible: this.props.visible })
}
componentWillReceiveProps(props) {
this.setState({ visible: props.visible })
}
closeModal() {
console.log('大家好,我叫取消,听说你们想点我?傲娇脸👸')
const { onClose } = this.props
onClose && onClose()
this.setState({ visible: false })
}
confirm() {
console.log('大家好,我叫确认,楼上的取消是我儿子,脑子有点那个~')
const { confirm } = this.props
confirm && confirm()
this.setState({ visible: false })
}
maskClick() {
console.log('大家好,我是蒙层,我被点击了')
this.setState({ visible: false })
}
render() {
const { visible } = this.state;
const { title, children } = this.props;
return <NewPortal>
{/* 引入transition组件,去掉了外层的modal-wrapper */}
<Transition
visible={visible}
transitionName="modal"
enterActiveTimeout={200}
enterEndTimeout={100}
leaveActiveTimeout={100}
leaveEndTimeout={200}
>
<div className="modal">
<div className="modal-title">{title}</div>
<div className="modal-content">{children}</div>
<div className="modal-operator">
<button
onClick={this.closeModal}
className="modal-operator-close"
>取消</button>
<button
onClick={this.confirm}
className="modal-operator-confirm"
>确认</button>
</div>
</div>
{/* 这里的mask也可以用transition组件包裹,添加淡入淡出的过渡效果,这里不再添加,有兴趣的读者可以自己实践下 */}
{/* <div
className="mask"
onClick={this.maskClick}
></div> */}
</Transition>
</NewPortal>
}
}
export default Modal;
На этом статья закончена.Для полноты прочтения,каждый шаг представляет собой размещенный полный код,что делает полный текст слишком длинным.Спасибо за прочтение.
Кодовый адрес этой статьи, добро пожаловать, звезда~