Mid-Autumn Festival holiday, a little boring, so write some blog warm heart, at the same time I wish you a happy Mid-Autumn Festival ~ 🙃

The next step is to walk you through the implementation of a basic modal popover component, encapsulating a simple animation component, and annotating some of the elements involved in the code.

I. Implementation of modal components;

1. Environment construction

We use the create-react-app command to quickly set up the development environment:

create-react-app modal
Copy the code

After the installation is complete, start the project as prompted, and then create a modal directory under SRC and modal.jsx modal.css

Modal.jsx has the following contents:

import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
  render() {
    return <div className="modal">This is a modal component</div>}}export default Modal;
Copy the code

Go back to the root directory, open app.js, and replace the contents with the following:

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;
Copy the code

After completing the above steps, our browser should look like this:

2. Perfect modal style

So before we do that, let’s think about what we normally use in a modal component: a header area, a content area, a control area, a mask;

Modal.jsx contents are modified as follows:

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">This is a modal heading</div>
        <div className="modal-content">This is modal content</div>
        <div className="modal-operator">
          <button className="modal-operator-close">cancel</button>
          <button className="modal-operator-confirm">confirm</button>
        </div>
      </div>
      <div className="mask"></div>
    </div>}}export default Modal;
Copy the code

The modal. CSS content is modified as follows:

.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;
}
Copy the code

After the modification is completed, our browser will display the following picture:

3. Development of modal function

Now that we’re ready to do that, let’s go ahead and implement modal, and again, when we use modal components, what are the basic functions?

  1. Can be achieved byvisiblecontrolmodalShow hidden;
  2. title.contentYou can customize the display content;
  3. I’m going to cancel and closemodalIs also called by the nameonClose, clicking ok will invoke the nameconfirmAnd closemodal, click on the maskmaskShut downmodal;
  4. The animate field turns animation on/off.

Add 3.1.visibleField controls explicit and implicit

Modal.jsx is modified as follows:

import React, { Component } from 'react';
import './modal.css';
class Modal extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    // The visile control passed through the parent component is explicit or implicit
    const { visible } = this.props;
    return visible && <div className="modal-wrapper">
      <div className="modal">
        <div className="modal-title">This is a modal heading</div>
        <div className="modal-content">This is modal content</div>
        <div className="modal-operator">
          <button className="modal-operator-close">cancel</button>
          <button className="modal-operator-confirm">confirm</button>
        </div>
      </div>
      <div className="mask"></div>
    </div>}}export default Modal;
Copy the code

App.js is modified as follows:

import Modal from './modal/modal';
import React, { Component } from 'react';

import './App.css';
class App extends Component {
  constructor(props) {
    super(props)
    // This is one of the binding methods. Since the method in the class is not automatically bound to the current example, we need to bind it manually. Otherwise, this would be undefined in the method.
    // The second method is to use the arrow function, such as: showModal = () => {}
    // The third method is to bind when called, such as 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;
Copy the code

Above, we passed the visible state in the parent component app.js to the modal component, and then controlled the visible value through the button click event to achieve the effect of controlling the visible and implicit modal component

The effect of unclicked button is shown below:

Click the button and the effect is as follows:

3.2. titlewithcontentContent customization

Modal.jsx is modified as follows:

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">{/* here use the parent's title*/}<div className="modal-title">{title}</div>{/* here the content uses the children of the parent */}<div className="modal-content">{children}</div>
        <div className="modal-operator">
          <button className="modal-operator-close">cancel</button>
          <button className="modal-operator-confirm">confirm</button>
        </div>
      </div>
      <div className="mask"></div>
    </div>}}export default Modal;
Copy the code

App.js is modified as follows:

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="Here's a custom title."
      >This is the custom content</Modal>
    </div>}}export default App;
Copy the code

Then we click the button on the page and the result is as follows:

3.3. Cancel and confirm buttons and add the mask click function

Think before you write: We need to close modal by clicking the cancel button, so we need to maintain a state in modal, and then use that state to control the visible and implicit of modal, which seems to work, but let’s think again, we used the visible of the parent component to control the visible of modal, so doesn’t that contradict us? That doesn’t work, so let’s make a change, so if the state of the parent changes, then we only update that state, and we click cancel in modal and we only update that state, and then we use that state value to control the implicit and explicit of modal; As for the onClose hook function, we can call it before updating the state, confirming the click of the button and canceling it.

Modal.jsx is modified as follows:

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}}// The first rendering uses the parent component's state to update the visible state in modal, which is called only once
  componentDidMount() {
    this.setState({ visible: this.props.visible })
  }

  // When you receive props, you update the visible state in modal according to the parent component's state
  componentWillReceiveProps(props) {
    this.setState({ visible: props.visible })
  }

  // Click to cancel the visible state in modal
  closeModal() {
    console.log('Hello, my name is Cancel, heard you want to order me? Tsundere face 👸 ')
    const { onClose } = this.props
    onClose && onClose()
    this.setState({ visible: false })
  }

  confirm() {
    console.log('Hello everyone, my name is Confirm, upstairs cancel is my son, a little brain ~')
    const { confirm } = this.props
    confirm && confirm()
    this.setState({ visible: false })
  }

  maskClick() {
    console.log('Hello, I'm Mask, I've been clicked')
    this.setState({ visible: false})
  }

  render() {
    // Use the visible state maintained in modal to control visibility and invisibility
    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"
          >cancel</button>
          <button
            onClick={this.confirm}
            className="modal-operator-confirm"
          >confirm</button>
        </div>
      </div>
      <div
        className="mask"
        onClick={this.maskClick}
      ></div>
    </div>}}export default Modal;
Copy the code

App.js is modified as follows:

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('I'm an onClose callback')
  }

  confirm() {
    console.log('THIS is confirm callback')
  }

  render() {
    const { visible } = this.state
    return <div className="app">
      <button onClick={this.showModal}>click here</button>
      <Modal
        visible={visible}
        title="Here's a custom title."
        confirm={this.confirm}
        onClose={this.closeModal}
      >This is the custom content</Modal>
    </div>}}export default App;
Copy the code

After saving, click cancel and confirm in the browser, and the console will appear as shown in the picture below:

4. Modal optimization

So that’s a basic modal component, but we have a problem with that, which is that the modal component that we’re introducing now is in an element called App, and some of the most widely used UI frameworks do have modal components in the Body layer, wherever you introduce them, This prevents the modal component from being interfered with by the parent’s style.

“To do this, we had to understand React’s own nature: Portals. This feature is added after the 16th version, and before 16 version, is through the use of ReactDOM unstable_renderSubtreeIntoContainer method processing, this method can render elements to the specified element, React-redux communicates across components based on context, so ReactDOM. Render will cause context loss. This invalidates all context-based frameworks for cross-component communication.

4.1. ReactDOM.unstable_renderSubtreeIntoContainerThe use of

ReactDOM.unstable_renderSubtreeIntoContainer(
  parentComponent, // To specify the context
  element,         // The element to render
  containerNode,   // Render to the specified DOM
  callback         / / callback
);
Copy the code

Oldportal. JSX = oldPortal. JSX = oldPortal. JSX = oldPortal. JSX = oldPortal. JSX

import React from 'react';
import ReactDOM from 'react-dom';

class OldPortal extends React.Component {
  constructor(props) {
    super(props)
  }

  // Initialize the visible property to determine whether to render
  componentDidMount() {
    const { visible } = this.props
    if (visible) {
      this.renderPortal(this.props); }}// Render and uninstall props each time it receives props
  componentWillReceiveProps(props) {
    if (props.visible) {
      this.renderPortal(props)
    } else {
      this.closePortal()
    }
  }

  / / rendering
  renderPortal(props) {
    if (!this.node) {
      // Prevent multiple node creation
      this.node = document.createElement('div');
    }
    // Add current node to body
    document.body.appendChild(this.node);

    ReactDOM.unstable_renderSubtreeIntoContainer(
      this.// Context specifies the current instance
      props.children, // The rendered element is the current children
      this.node,      // Render the element into our new Node without using the fourth parameter callback.
    );
  }

  / / unloading
  closePortal() {
    if (this.node) {
      // Unload components from the element
      ReactDOM.unmountComponentAtNode(this.node)
      // Remove the element
      document.body.removeChild(this.node)
    }
  }

  render() {
    return null; }}export default OldPortal
Copy the code

After saving, we use it in 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('Hello, my name is Cancel, heard you want to order me? Tsundere face 👸 ')
    const { onClose } = this.props
    onClose && onClose()
    this.setState({ visible: false })
  }

  confirm() {
    console.log('Hello everyone, my name is Confirm, upstairs cancel is my son, a little brain ~')
    const { confirm } = this.props
    confirm && confirm()
    this.setState({ visible: false })
  }

  maskClick() {
    console.log('Hello, I'm Mask, I've been clicked')
    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"
            >cancel</button>
            <button
              onClick={this.confirm}
              className="modal-operator-confirm"
            >confirm</button>
          </div>
        </div>
        <div
          className="mask"
          onClick={this.maskClick}
        ></div>
      </div>
    </OldPortal>}}export default Modal;
Copy the code

As you can see, we just wrap a layer of OldPortal component around the return content in modal, and then pass the state visible that controls explicit and implicit to OldPortal component, OldPortal actually controls the explicit and implicit of modal. Then we clicked on the button in the page and opened the console at the same time, and found modal as we thought, the bed was sent to the body layer:

Version 4.2. 16Portaluse

In version 16, react-DOM native provides a method, reactdom.createPortal (), to implement portal functionality:

ReactDOM.createPortal(
  child,    // The element to render
  container // Specify the parent element to render
)
Copy the code

Reduce the two parameters than unstable_renderSubtreeIntoContainer, then we use it in the project.

In the SRC directory newPortal new directory in which the new newPortal. JSX, newPortal. JSX content is as follows:

import React from 'react';
import ReactDOM from 'react-dom';

class NewPortal extends React.Component {
  constructor(props) {
    super(props)
    // Initialize the parent element that created the render and add it under body
    this.node = document.createElement('div');
    document.body.appendChild(this.node);
  }

  render() {
    const { visible, children } = this.props;
    // Direct explicit implicit representation
    return visible && ReactDOM.createPortal(
      children,
      this.node, ); }}export default NewPortal
Copy the code

Can see clear content contrast unstable_renderSubtreeIntoContainer implementation simplifies a lot, then we in modal. JSX used in:

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('Hello, my name is Cancel, heard you want to order me? Tsundere face 👸 ')
    const { onClose } = this.props
    onClose && onClose()
    this.setState({ visible: false })
  }

  confirm() {
    console.log('Hello everyone, my name is Confirm, upstairs cancel is my son, a little brain ~')
    const { confirm } = this.props
    confirm && confirm()
    this.setState({ visible: false })
  }

  maskClick() {
    console.log('Hello, I'm Mask, I've been clicked')
    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"
            >cancel</button>
            <button
              onClick={this.confirm}
              className="modal-operator-confirm"
            >confirm</button>
          </div>
        </div>
        <div
          className="mask"
          onClick={this.maskClick}
        ></div>
      </div>
    </NewPortal>}}export default Modal;
Copy the code

Using the same OldPortal, let’s take a look in the browser to see if it works as we thought:

“Portals are the soul of a popover class of components. Their use here serves only as a guide to their core functionality, but does not go into the depth of some complex public approach. Interested readers can search for related articles that are explained in more detail.

2. Animation implementation of entry

1. Animation addition

Starting with a simple effect (using the code above for the Modal component using the NewPortal component), the Modal is gradually enlarged to 1.1 times when it pops up, then reduced to 1 times when it hides, first enlarged to 1.1 times, then reduced until it disappears.

The convention starts by thinking: What do we control to zoom in and out? How do we change the process of zooming in and out from an instant to a gradual process? When do we start zooming in and out? And when do you end up zooming in and out?

Zoom in and out we pass the CSS3 attribute transform We can use the scale parameter for each of the six states, and then we can use the transition to zoom in and out. We can use the scale parameter for each of the six states Excessive, should be able to achieve the effect we need:

Add the following code to 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;
}
Copy the code

The six class names define the six states that appear and disappear, and set the transition time of each. Then we can add the corresponding class name to the element in the different process, and can control the display state of the element.

Before we write the logic, we should also note that the implicit and explicit aspects of our components were actually controlled in the NewPortal component, but when we added the animation to the Modal component, we had to control the timing of the implicit and explicit aspects, such as starting the animation right after rendering and hiding after the animation, which would not be suitable for NewPortal The intermediate control is hidden. Some readers have wondered, why not just add animations to the NewPortal component? Of course the answer to this question is yes, but NewPortal’s function is delivery, not complex animation, and we want to keep it pure and not coupling with other components.

Modify newportal. JSX as follows:

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
Copy the code

Modify modal.jsx as follows:

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) {
      // When the parent component props is received, render the animation if it is true
      this.enterAnimate()
    }
  }

  // Enter the animation
  enterAnimate() {
    // Here we define the class name for each state, which is the class we added earlier in the modal.css file
    const enterClasses = 'modal-enter'
    const enterActiveClasses = 'modal-enter-active'
    const enterEndActiveClasses = 'modal-enter-end'
    // This defines the transition time of each state, which corresponds to the transition time of the corresponding class name in modal. CSS, in milliseconds
    const enterTimeout = 0
    const enterActiveTimeout = 200
    const enterEndTimeout = 100
    // Change the explicit and implicit state to true and change classes to the name of the class in the Enter state
    this.setState({ visible: true.classes: enterClasses })
    // We use the timer because the function in the timer will be added to the event queue and will not be called until the main thread task has completed, which is equivalent to the enterTimeout time after the element is rendered and the initial class name is added.
    // Since the start state doesn't need to be excessive, we'll just set it to 0.
    const enterActiveTimer = setTimeout(_= > {
      this.setState({ classes: enterActiveClasses })
      clearTimeout(enterActiveTimer)
    }, enterTimeout)
    const enterEndTimer = setTimeout(_= > {
      this.setState({ classes: enterEndActiveClasses })
      clearTimeout(enterEndTimer)
    }, enterTimeout + enterActiveTimeout)

    // Finally, leave the class name blank to restore the element to its original state
    const initTimer = setTimeout(_= > {
      this.setState({ classes: ' ' })
      clearTimeout(initTimer)
    }, enterTimeout + enterActiveTimeout + enterEndTimeout)
  }

  // Leave the animation
  leaveAnimate() {
    const leaveClasses = 'modal-leave'
    const leaveActiveClasses = 'modal-leave-active'
    const leaveEndActiveClasses = 'modal-leave-end'
    const leaveTimeout = 0
    const leaveActiveTimeout = 100
    const leaveEndTimeout = 200
    // The initial element already exists, so there is no need to change the explicit or implicit state
    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)
    // Finally change the explicit and implicit state to false and restore the class name to the initial state
    const initTimer = setTimeout(_= > {
      this.setState({ visible: false.classes: ' ' })
      clearTimeout(initTimer)
    }, leaveTimeout + leaveActiveTimeout + leaveEndTimeout)
  }

  closeModal() {
    console.log('Hello, my name is Cancel, heard you want to order me? Tsundere face 👸 ')
    const { onClose } = this.props
    onClose && onClose()
    // Click Cancel after calling leave animation
    this.leaveAnimate()
  }

  confirm() {
    console.log('Hello everyone, my name is Confirm, upstairs cancel is my son, a little brain ~')
    const { confirm } = this.props
    confirm && confirm()
    this.leaveAnimate()
  }

  maskClick() {
    console.log('Hello, I'm Mask, I've been clicked')
    this.setState({ visible: false })
  }

  render() {
    const { visible, classes } = this.state;
    const { title, children } = this.props;
    return <NewPortal>
      <div className="modal-wrapper">
        {
          visible &&
          <div className={`modalThe ${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"
              >cancel</button>
              <button
                onClick={this.confirm}
                className="modal-operator-confirm"
              >confirm</button>
            </div>
          </div>}} {/* * * * * * * * * * * * * * *<div
          className="mask"
          onClick={this.maskClick}
        ></div>* /}</div>
    </NewPortal>}}export default Modal;
Copy the code

The effect is as follows:

2. Animation component encapsulation

The animation is implemented, but the code is all in modal. JSX, which is not elegant at all and is not reusable, so we need to consider abstracting it into a Transition component.

Idea: Let’s think about encapsulation in terms of the functionality points we need. First, the incoming explicit or implicit state value controls the explicit or implicit of the element. Given a class name that matches the corresponding six state class names; You can configure the transition time for each state; You can control whether animation is used or not;

Create a file transition.jsx in the SRC directory and create the following file:

import React from 'react';
// classnames are introduced to handle concatenation of 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,}}// The transition time is not passed in by default to 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,}// Here we add the first render animation. It only happens once
  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)
    }
  }

  // First rendering of the entry animation
  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)
  }

  // Entry animation
  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)
  }

  // Exit animation
  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)
  }

  // Unified class name configuration
  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

    // Add extra props to child elements via React. CloneElement,
    return React.cloneElement(
      children,
      { className: `${className} ${classes}` }
    )
  }


  render() {
    const { visible } = this.state
    return visible && this.cloneChildren()
  }
}

export default Transition
Copy the code

Modal.jsx contents are modified as follows:

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('Hello, my name is Cancel, heard you want to order me? Tsundere face 👸 ')
    const { onClose } = this.props
    onClose && onClose()
    this.setState({ visible: false })
  }

  confirm() {
    console.log('Hello everyone, my name is Confirm, upstairs cancel is my son, a little brain ~')
    const { confirm } = this.props
    confirm && confirm()
    this.setState({ visible: false })
  }

  maskClick() {
    console.log('Hello, I'm Mask, I've been clicked')
    this.setState({ visible: false })
  }

  render() {
    const { visible } = this.state;
    const { title, children } = this.props;
    return <NewPortal>{/* Introduce the transition component, removing the outer 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"
            >cancel</button>
            <button
              onClick={this.confirm}
              className="modal-operator-confirm"
            >confirm</button>
          </div>
        </div>{/* this mask can also be wrapped with the transition component, adding a fade in and out effect, but not here, you can try it yourself */} {/*<div
          className="mask"
          onClick={this.maskClick}
        ></div>* /}</Transition>
    </NewPortal>}}export default Modal;
Copy the code

This is the end of the article. For the sake of reading integrity, each step is the complete code posted, resulting in a long text. Thank you for your reading.

This article code address, welcome star~