preface

When the company developed project A some time ago, the project involved many modules, and many of them were quite independent, such as editor module, visual editing module, dynamic form custom configuration module, navigation bar and permission page configuration module, etc.

And part function modules in the project B, C programs are used to, so, taken together, these modules or as a separate component module development is more appropriate, through unified package management, such as NPM, other staff and project quick add can be easy to use, avoid duplication of effort, also avoid copying and pasting this not easy maintenance and stable and synchronous update trouble ~

Many of these scenarios require values to be passed across component modules. The normal way to use components is to import a component module through import and then receive it through props, similar to antD. Child1 and Child2 can communicate with each other using the Parent component (props) and the Parent component (props). If the Parent component (props) is used to communicate with each other, then the Parent component (props) can communicate with the other component (props)

Take a quick look at the code and logical structure

Code structure

import React from 'react'
import Child1 from 'M' / / NPM package M
import Child2 from 'N' / / NPM package N

const Parent:React.FC=() = >{

    return <div>
        <Child1 />
        <Child2 />{/* Other business components, etc. */} {/*... * /}</div>
}

export default  Parent
Copy the code

Relationships between components

As shown in the figure, the component of Child1 is divided into many child and child modules. In this case, we need to communicate with the E module in Child1, which is deeply nested. Both the props and Context modes have certain cost and coupling

Several modes of event communication

✨ Event Bus

This is mostly done through the publish-subscribe model, which I wrote about in another article on publish-subscribe versus observer

Specific implementation and principle will not say, simply look at the way of use

yarn add events

Create SRC /utils/ eventbus. ts and instantiate it to make sure that the instance is unique and should be referenced whenever it is used

import { EventEmitter } from 'events'

// Ensure a unique instance
const EventBus = new EventEmitter()

export default EventBus
Copy the code

Let’s do a quick demo test

The directory structure is as follows

Active - Active - Active - Active - Active - Active - Active - Active - ActiveCopy the code

The Parent component

import React from "react"
import Child1 from "./Child1"
import Child2 from "./Child2"

const Parent: React.FC = () = > {
  return (
    <div className="parent-container">
      <div className="content-box">
        <Child1 />
        <Child2 />
      </div>
    </div>)}export default Parent
Copy the code

Child1 component 1

import React, { useEffect, useState } from "react"
import EventBus from ".. /utils/eventBus"

interface StateProps {
  name: string
  age: number
  count: number
}

const PERSON_INIT: StateProps = {
  name: "Xiao Ming".age: 18.count: 0,}const Child1: React.FC = () = > {
  const [state, setState] = useState<StateProps>(PERSON_INIT) // State of this component
  const [recevie, setRecevie] = useState() // Receive state from other components

  const eventRegister = (args: any) = > {
    console.log("I am Child1 and I have received a message from Child2:", args)
    setRecevie(args)
  }

  useEffect(() = > {
    // mount
    EventBus.on("msgTochild1", eventRegister)

    // unmount
    return () = > {
      EventBus.off("msgTochild1", eventRegister)
    }
  }, [])

  const sendMsgToChild2 = () = > {
    EventBus.emit("msgTochild2", state)
  }

  return (
    <div>
      <h2>Child1</h2>
      <article>
        <p>state:</p>
        <pre>{JSON.stringify(state, null, 2)}</pre>
        <br />
        <p>event recevie:</p>
        <pre>{JSON.stringify(recevie, null, 2)}</pre>
        <br />
        <button onClick={()= >setState((prev) => ({ ... prev, count: prev.count + 1 }))}> count + 1</button>
        <button onClick={sendMsgToChild2}>sendMsgToChild2</button>
      </article>
    </div>)}export default Child1
Copy the code

Child2 component 2

import React, { useEffect, useState } from "react"
import EventBus from ".. /utils/eventBus"

interface ListProps {
  name: string
  age: number
  count: number
}

const LIST_INIT: ListProps[] = [
  {
    name: "Zhang".age: 10.count: 0,},]const Child2: React.FC = () = > {
  const [list, setlist] = useState<ListProps[]>(LIST_INIT) // State of this component
  const [recevie, setRecevie] = useState() // Receive state from other components

  const eventRegister = (args: any) = > {
    console.log("I am Child2 and I have received a message from Child1:", args)
    setRecevie(args)
  }

  useEffect(() = > {
    // mount
    EventBus.on("msgTochild2", eventRegister)

    // unmount
    return () = > {
      EventBus.off("msgTochild2", eventRegister)
    }
  }, [])

  const sendMsgToChild1 = () = > {
    EventBus.emit("msgTochild1", list)
  }

  return (
    <div>
      <h2>Child2</h2>
      <article>
        <p>list:</p>
        <pre>{JSON.stringify(list, null, 2)}</pre>
        <br />
        <p>event recevie:</p>
        <pre>{JSON.stringify(recevie, null, 2)}</pre>
        <br />
        <button onClick={()= >Setlist ((prev) = > [... prev, {name: "xiao li", the age: 11, count: 0}])} > count + 1</button>
        <button onClick={sendMsgToChild1}>sendMsgToChild1</button>
      </article>
    </div>)}export default Child2
Copy the code

Look at the effect

The status update can be done with callback, but there is a problem

First of all, we need to communicate with the E module of the Child1 component. So far, this simple demo only verifies the feasibility of the eventBus communication, i.e. Child1 to Child2 communication

Child1 is actually a demo component that we abstract from. In the actual scene, it should be a component that we introduce through NPM package -M. Therefore, it seems that we still need to pass value communication to M component through props

Because eventBus needs to be unique, and our eventBus instance is in the parent component’s project, the M component can’t get the instantiated eventBus directly. We can’t pass the eventBus through Props

The current solution is to mount the instantiated eventBus to a place that the component can access without using props

  • windowGlobal properties
// The Parent component mounts EventBus to the window
import EventBus from ".. /utils/eventBus"
window._MY_EVENTBUS_ = EventBus

/ / M components
const EventBus = window._MY_EVENTBUS_
Copy the code

Under the current project, the Window is globally unique and can be accessed by any component without limitation, which seems to be fine

But there are risks

  • Variable pollution
  • security

Therefore, the use of ~ is not recommended

✨JavaScript custom events

Use JavaScript native custom events and customEvents directly to see how they are used

1. 🤓 uses JavaScript’s built-in Event constructor

const myEvent = new Event(typeName, option)

  • TypeName:DOMStringType, which represents the name of the creation event.
  • Option: optional configuration item
    • bubbles: indicates whether the event bubbles. Defaultnull
    • cancelable: indicates whether the event can be canceled. Default valuefalse
    • composed: indicates whether the event triggers a listener outside of the shadow DOM root node (shadow DOM:Shadow DOM), the defaultfalse

The sample

// Create A bubbling event-a event
const myEvent = new Event("event-A", { bubbles: true })

// Trigger the event
document.dispatchEvent(myEvent);
Copy the code

Event communication is fine, but this method does not support direct passing of parameter values, this method needs to be considered

2. 🤓 uses JavaScript’s built-in CustomEvent constructor

const myEvent = new CustomEvent(typeName, option)

  • TypeName:DOMStringType, which represents the name of the creation event.
  • Option: optional configuration item
    • detail: Indicates the data to be passed in the event, retrieved in EventListener, defaultnull
    • bubbles: indicates whether the event bubbles. Defaultfalse
    • cancelable: indicates whether the event can be cancelled. Shadow DOM:Shadow DOM), the defaultfalse

The sample

// Create an event
const myEvent = new CustomEvent("eventName", { detail: list })
// Add event listener
window.addEventListener("eventName".e= > console.log(e))
// Send events
window.dispatchEvent(myEvent)
Copy the code

Same simple write a demo test

The directory structure is as follows

Components -customEvent ├─ Parent ├─ Child1 ├─ Child2Copy the code

The Parent component remains the same as above

Child1 component 1

import React, { useEffect, useState } from "react"

interface StateProps {
  name: string
  dec: string
  count: number
}

const PERSON_INIT: StateProps = {
  name: "Xiao Ming".dec: "Child1 CustomEvent CustomEvent event-a".count: 0,}const Child1: React.FC = () = > {
  const [state, setState] = useState<StateProps>(PERSON_INIT)
  const [recevie, setRecevie] = useState()

  const eventRegister = (args: any) = > {
    console.log("I am Child1 and I have received a message from Child2:", args)
    setRecevie(args.detail)
  }

  useEffect(() = > {
    // mount
    window.addEventListener("event-B", eventRegister)

    // unmount
    return () = > {
      window.removeEventListener("event-B", eventRegister)
    }
  }, [])

  const sendMsgToChild2 = () = > {
    // Create an event
    const myEvent = new CustomEvent("event-A", { detail: state })
    // Send events
    window.dispatchEvent(myEvent)
  }

  return (
    <div>
      <h2>Child1</h2>
      <article>
        <p>state:</p>
        <pre>{JSON.stringify(state, null, 2)}</pre>
        <br />
        <p>event recevie:</p>
        <pre>{JSON.stringify(recevie, null, 2)}</pre>
        <br />
        <button onClick={()= >setState((prev) => ({ ... prev, count: prev.count + 1 }))}> count + 1</button>
        <button onClick={sendMsgToChild2}>sendMsgToChild2</button>
      </article>
    </div>)}export default Child1
Copy the code

Child2 component 2

import React, { useEffect, useState } from "react"

interface ListProps {
  name: string
  dec: string
  count: number
}

const LIST_INIT: ListProps[] = [
  {
    name: "Xiao li".dec: "Child2 CustomEvent CustomEvent event-b".count: 0,},]const Child2: React.FC = () = > {
  const [list, setlist] = useState<ListProps[]>(LIST_INIT)
  const [recevie, setRecevie] = useState()

  const eventRegister = (args: any) = > {
    console.log("I am Child2 and I have received a message from Child1:", args)
    setRecevie(args.detail)
  }

  useEffect(() = > {
    // mount
    window.addEventListener("event-A", eventRegister)

    // unmount
    return () = > {
      window.removeEventListener("event-A", eventRegister)
    }
  }, [])

  const sendMsgToChild1 = () = > {
    // Create an event
    const myEvent = new CustomEvent("event-B", { detail: list })
    // Send events
    window.dispatchEvent(myEvent)
  }

  return (
    <div>
      <h2>Child2</h2>
      <article>
        <p>list:</p>
        <pre>{JSON.stringify(list, null, 2)}</pre>
        <br />
        <p>event recevie:</p>
        <pre>{JSON.stringify(recevie, null, 2)}</pre>
        <br />
        <button
          onClick={()= >Setlist ((prev) => [...prev, {name: "prev ", dec: "Child2 CustomEvent event-b ", count: 0},])} > person list + 1</button>
        <button onClick={sendMsgToChild1}>sendMsgToChild1</button>
      </article>
    </div>)}export default Child2
Copy the code

Look at the effect

It can be seen that this method supports event communication and parameter value transmission, which can basically meet our needs

The addEventListener works the same as a regular event. After adding a subscription, the callback receives the event’s values and status updates, but there are some differences

That is, instead of having to use a single instance like EventBus, it can be triggered anywhere by specifying the typeName of a custom event, making it easier to use

I don’t know if there is a pit

Look at the compatibility, as follows

😆, IE browser does not support CustomEvent. Detail. Edge 14+ has just started to support CustomEvent.

Here is a Polyfill scheme provided by Zhang Xinxu’s blog, CV ~

/** * CustomEvent constructor polyfill for IE */
(function () {
    if (typeof window.CustomEvent === 'function') {
        // If it is not IE
        return false;
    }

    var CustomEvent = function (event, params) {
        params = params || {
            bubbles: false.cancelable: false.detail: undefined
        };
        var evt = document.createEvent('CustomEvent');
        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
        return evt;
    };

    CustomEvent.prototype = window.Event.prototype;

    window.CustomEvent = CustomEvent; }) ();Copy the code

conclusion

OK, that’s it

Event is essentially a message, and event pattern is essentially the realization of observer pattern, that is, where observer pattern can be used, event pattern can also be used.

We’re back in observer mode

reference

  • CustomEvent – MDN
  • JavaScript custom events are so easy!
  • JS CustomEvent CustomEvent parameter transfer tips