The smallest logical unit in React is a component. If there is a coupling relationship between components, they will communicate with each other. This article will introduce different communication modes of components in React

By inductive norm, communication between arbitrary components can be classified into four types of communication between components, which are parent-child component, parent-child component, sibling component and arbitrary component respectively. It should be noted that the first three can also be counted as the category of any component, so the last one is universal method

Father and son components

The communication between parent and child components is divided into two situations: parent component communicates with child component and child component communicates with parent component. The following is the introduction of parent component communicates with child component. The traditional method is divided into two situations, namely parameter transfer during initialization and method call during instance stage, as shown in the following example

class Child {
    constructor(name) {
        // Get the DOM reference
        this.$div = document.querySelector('#wp');

        // Name is passed in for initialization
        this.updateName(name);
    }
    updateName(name) {
        // Provide updated API externally
        this.name = name;
    
        / / update the dom
        this.$div.innerHTML = name; }}class Parent {
    constructor() {
        // Initialization phase
        this.child = new Child('yan');
        
        setTimeout((a)= > {
            // Instantiation phase
            this.child.updateName('hou');
        }, 2000); }}Copy the code

React handles both cases in the same way, using properties. This is possible because React automatically re-renders the subcomponents when the properties are updated. In the following example, the subcomponents will automatically re-render after 2 seconds and acquire new property values

class Child extends Component {
    render() {
        return <div>{this.props.name}</div>}}class Parent extends Component {
    constructor() {
        // Initialization phase
        this.state = {name: 'yan'};

        setTimeout((a)= > {
            // Instantiation phase
            this.setState({name: 'hou'})},2000);
    }
    render() {
        return <Child name={this.state.name} />}}Copy the code

Now let’s look at how a component communicates with its parent. Traditionally, there are two ways to do this: one is to call back functions, and the other is to deploy message interfaces for children

The advantage of callbacks is that they are very simple. The disadvantage is that they must be passed in at initialization, and cannot be withdrawn, and can only be passed in one function

class Child {
    constructor(cb) {
        // Call the callback function passed in by the parent component to send the message
        setTimeout((a)= > { cb() }, 2000); }}class Parent {
    constructor() {
        // The callback function is passed in during initialization
        this.child = new Child(function () {
            console.log('child update')}); }}Copy the code

Let’s look at the message interface method, first of all, we need a base class that can publish and subscribe messages, such as the following implementation of a simple EventEimtter, the actual production can directly use the class library written by others, such as @jsmini/ Event, subcomponents inherit the message base class, have the ability to publish messages, Then the parent component subscribes to the message of the child component, and the function of the child component communicating to the parent component can be realized

The advantage of the message interface is that you can subscribe anywhere, multiple times, and unsubscribe, but the disadvantage is that you need to introduce a message base class

// Message interface, subscription publishing mode, similar to binding event, trigger event
class EventEimtter {
    constructor() {
        this.eventMap = {};
    }
    sub(name, cb) {
        const eventList = this.eventMap[name] = this.eventMap[name] || {}; eventList.push(cb); } pub(name, ... data) { (this.eventMap[name] || []).forEach(cb= > cb(...data));
    }
}

class Child extends EventEimtter {
    constructor() {
        super(a);// Publish messages through the message interface
        setTimeout((a)= > { this.pub('update')},2000); }}class Parent {
    constructor() {
        // The callback function is passed in during initialization
        this.child = new Child();
        
        // Subscribes to messages for subcomponents
        this.child.sub('update'.function () {
            console.log('child update')}); }}Copy the code

Backbone.js supports both callback function and message interface mode, but React uses the simple callback function mode

class Child extends Component {
    constructor(props) {
        setTimeout((a)= > { this.props.cb() }, 2000);
    }
    render() {
        return <div></div>}}class Parent extends Component {
    render() {
        return <Child cb={()= > {console.log('update')}} />
    }
}

Copy the code

Ye sun components

Parent-child component can be a special case of the ye sun components, ye sun component here not only refers to the grandpa and grandson, but communication refers to the ancestors and descendants components, may through the many levels, we have solved the problem of the communication component, father and son according to the method of transforming, it is easy to draw ye sun component of the answer, that is layer upon layer transfer properties? The problem of dividing parent-child communication into multiple parent-child communication

The advantage of layer-by-layer transfer is that it is very simple and can be solved with existing knowledge. The problem is that it will waste a lot of code and be very tedious. The components acting as Bridges in the middle will introduce many attributes that do not belong to them

React uses context to allow ancestor components to pass properties directly to descendant components, much like a wormhole in star Trek. Context is a special bridge that allows messages to be passed to descendant components across any level

How do you open the wormhole between components that need to communicate? You need a bidirectional declaration, that is, you declare the properties on the ancestor component, you declare them again on the descendant component, and then you put the properties on the ancestor component, and then you can read the properties on the descendant component. Here’s an example

import PropTypes from 'prop-types';

class Child extends Component {
    // Descendant component declarations need to read data on the context
    static contextTypes = {
        text: PropTypes.string
    }
    render() {
        // Read the data on the context through this.context
        return <div>{this.context.text}</div>}}class Ancestor extends Component {
    // The ancestor component declares the data that needs to be placed on the context
    static childContextTypes = {
        text: PropTypes.string
    }
    // Ancestor components put data into context
    getChildContext() {
        return {text: 'yanhaijing'}}}Copy the code

The advantage of context is that it can save the trouble of passing layers and control the visibility of data through two-way declaration. For many layers, it is a solution. But the disadvantages are also obvious, just like global variables, if not controlled it is easy to cause confusion, but also prone to overlapping problems

My personal suggestion is to use context to pass read-only information shared by all components, such as login user information, etc

Tip: React Router uses the context to pass route attributes

Brother components

If two components are siblings, they can communicate with each other using the parent component as a bridge, which is essentially the master module pattern

In the following example, the two child components use the parent component to display digital synchronization

class Parent extends Component {
    constructor() {
        this.onChange = function (num) {
            this.setState({num})
        }.bind(this);
    }
    render() {
        return( <div> <Child1 num={this.state.num} onChange={this.onChange}> <Child2 num={this.state.num} onChange={this.onChange}> </div> ); }}Copy the code

The advantage of the main module pattern is decoupling, decoupling the coupling between the two child components into the coupling between the child component and the parent component. The benefits of collecting scattered things together are very obvious, leading to better maintainability and extensibility

Any component

Any component includes the above three relational components, which should use the method described above in preference. There are three methods for communication between any two components, namely common ancestor, message-oriented middleware, and state management

Based on the above described ye sun components and brother, as long as find the common ancestor of two components, it can be any communication between components, into any communication between the components and common ancestor, the advantages of this method is very simple, the known knowledge can fix, faults is the superposition of the drawback of the above two models, in addition to the temporary solution, using this method is not recommended

Another common approach is messaging middleware, which introduces a global messaging tool, through which two components communicate, so that the communication between the two components is completed through the global messaging medium

Remember the message base classes introduced above? In the following example, component 1 and component 2 communicate via a global event

class EventEimtter {
    constructor() {
        this.eventMap = {};
    }
    sub(name, cb) {
        const eventList = this.eventMap[name] = this.eventMap[name] || {}; eventList.push(cb); } pub(name, ... data) { (this.eventMap[name] || []).forEach(cb= > cb(...data));
    }
}

// Global messaging tool
const event = new EventEimtter;

// a component
class Element1 extends Component {
    constructor() {
        // Subscribe to the message
        event.sub('element2update', () = > {console.log('element2 update')}); }}// Another component.
class Element2 extends Component {
    constructor() {
        // Publish the message
        setTimeout(function () { event.pub('element2update')},2000)}}Copy the code

Message middleware model is very simple, using the observer pattern, the coupling decoupling between two components into the component and message center + message name coupling, but for decoupling is introduced global message center and the name of the message center invasive of components is very strong, and the third party components can’t use this way of communication

Small projects are more suitable to use this method, but with the expansion of the project scale, after reaching the medium project, the message name explosion growth, message name maintenance becomes a tricky problem, the probability of the same name is very high, no one dare to delete the message information, the message publisher can not find the information of the message subscribers

In fact, the above problems are not without solutions. The problem of the same name can be greatly reduced through the formulation of norms, message namespace and other ways, while other problems can be easily solved by the unified maintenance of message names into a file through the centralized management of messages

If your project is too large for either of these options, you may need a state management tool that abstracts the relationships between components and their processing logic into a centralized place. Redux is a great state management tool for this purpose

In addition to Redux, there are also Mobx, Rematch, ResELECT, etc., which will not be covered in this article, but will be written separately. These tools are designed to solve different problems, just choose the right tool for your scenario

conclusion

For projects of different scales, you should choose your own technical solution. The decoupling degree of the different ways described above is different. For the good or bad of different coupling relationships, please refer to my previous article “Diagram of 7 Coupling Relationships”.

This article is excerpted from my new book React State Management and Isomer Combat. If you are interested, you can continue to read this book. This book is jointly honed by Me and my own technology Hou Ce, which condenses what we have accumulated and learned in the process of learning and practicing React framework. In addition to the introduction of React framework, this article focuses on the analysis of state management and server rendering isomorphism applications. At the same time, a large number of excellent ideas from the community were drawn up and compared.

The book was written by Baidu vice President Shen Dou, Baidu senior front End engineer Dong Rui, Ruan Yifeng, a well-known expert on JavaScript, Wolf Shu, a nojue advocate, JustJavac, the founder of Flarum Chinese community, Xiao Jue, a technical expert on Sina mobile front end, Gu Yiling, a well-known blogger of Zhihu, and other experts in the front end of jue.

Interested readers can click on the link below to buy, thank you again for your support and encouragement! Please criticize and correct!

Jingdong: item.jd.com/12403508.ht…

Down-down: product.dangdang.com/25308679.ht…

The original url: yanhaijing.com/javascript/…