Пошаговое руководство по инкапсуляции модального компонента, основанного на реакции.

внешний интерфейс JavaScript React.js

Праздник середины осени немного скучен, поэтому я пишу сообщение в блоге, чтобы согреть свое сердце, и желаю всем счастливого праздника середины осени~ 🙃

Далее я покажу вам шаг за шагом, как реализовать базовый компонент модального всплывающего окна и инкапсулировать простой компонент анимации.

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. развитие модальной функции

На этом наши приготовления завершены.Далее мы будем реализовывать модальную функцию.Напомним, когда мы будем использовать модальную составляющую, какие основные функции будут?

  1. в состоянии пройтиvisibleконтрольmodalявные и неявные;
  2. title,contentСодержимое дисплея можно настроить;
  3. Нажмите Отмена, чтобы закрытьmodal, который также вызываетonCloseОбратный вызов, нажав OK, вызовет обратный вызов с именемconfirmобратный вызов и закрытьmodal, нажмите на маскуmaskзакрытиеmodal;
  4. Поле анимации может включать/выключать анимацию;

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;

На этом статья закончена.Для полноты прочтения,каждый шаг представляет собой размещенный полный код,что делает полный текст слишком длинным.Спасибо за прочтение.

Кодовый адрес этой статьи, добро пожаловать, звезда~