Come and join us!

“The Newbies of Little Wo Shan” provides front-end developers with technical information and a series of basic articles. For a better user experience, please go to xhs-rookies.com/ to learn the latest articles.

“Code Tailor “, if you are interested in our article, or would like to make some suggestions, please follow the official account of “Rookie of Xiaohe Mountain” on wechat and contact us. You can also view our article on wechat. Every suggestion or approval is a great encouragement to us!

preface

This section we will introduce the React of china-africa parent-child communication of components, on the day we say to parent-child communication between components can be finished through the props and the callback function, but as the application is more and more big, the use of props and callback function becomes very complicated, so the components of the communication between the father and son components, is there a simple way?

This article introduces you to the following:

  • Communication between components across levels
  • Context
  • Sibling communication

Communication between components across levels

Context

Context provides a way to pass data across the component tree without manually adding props for each layer of components

Context usage scenarios

  • For scenarios where some data needs to be shared among multiple components (locale preference,UISubject, user login status, user information, etc.).
  • If we were on the top floorAppFor components that do not need data in the middle layer, this is a redundant operation.

If there are more layers, passing layers is cumbersome and the code is very redundant:

  • ReactProvided aAPI: Context;
  • ContextProvides a way to share such values between components without having to explicitly pass them layer by layer through the component treeprops;
  • ContextIt is designed to share data that is “global” to a component tree, such as currently authenticated users, topics, or preferred languages;

Context related API

React.createContext
const MyContext = React.createContext(defaultValue)
Copy the code

Create a shared Context object:

  • If a component subscribesContext, the component will match from the one closest to itselfProviderRead to the currentcontextValue;
  • Only if there is no match in the tree of the componentProviderWhen thedefaultValueParameter takes effect.defaultValueThe component did not find the corresponding in the top-level lookupProvider, then use the default values

Note: When undefined is passed to the Provider’s value, the consumer component’s defaultValue does not take effect.

Context.Provider
<MyContext.Provider value={/* Some value */} >Copy the code

Each Context object returns a Provider React component that allows the consuming component to subscribe to changes to the Context:

  • ProviderTo receive avalueProperty, passed to the consuming component;
  • aProviderCan be associated with multiple consumer components;
  • multipleProviderIt can also be nested, with the inner layer overwriting the outer data.

When a Provider’s value changes, all its internal consumer components are rerendered;

Class.contextType
class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context
    /* After the component is mounted, use the value of the MyContext component to perform some side effects */
  }
  componentDidUpdate() {
    let value = this.context
    / *... * /
  }
  componentWillUnmount() {
    let value = this.context
    / *... * /
  }
  render() {
    let value = this.context
    /* Render based on the value of the MyContext component */
  }
}
MyClass.contextType = MyContext
Copy the code

The contextType property mounted on the class is reassigned to a Context object created by react.createcontext () :

  • This allows you to usethis.contextTo consume recentlyContextThe value of;
  • You can access it in any lifecycle, includingrenderIn the function;
Context.Consumer
<MyContext.Consumer>
  {value= > /* Render based on context value */}
</MyContext.Consumer>
Copy the code

Here, the React component can also subscribe to context changes. This allows you to subscribe to the context in a functional component.

  • Here we need the function as a child (function as child) this practice;
  • This function accepts the currentcontextValue, returns oneReactNode;

The Context used

For example, in the following code, we manually style a button component using a “theme” property:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />}}function Toolbar(props) {
  // The Toolbar component accepts an additional "theme" property, which is passed to the ThemedButton component.
  // If every single button in your application needs to know the theme value, this would be a hassle,
  // Because this value must be passed layer by layer to all components.
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>)}class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />}}Copy the code

Using context, we can avoid passing props through intermediate elements:

// Context allows us to pass values deep into the component tree without explicitly traversing each component.
// Create a context for the current theme (" light "is the default).
const ThemeContext = React.createContext('light')
class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the following component tree.
    // Any component, no matter how deep, can read this value.
    // In this case, we pass "dark" as the current value.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>)}}// Intermediate components no longer have to specify the theme to pass down.
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>)}class ThemedButton extends React.Component {
  // Specify contextType to read the current theme context.
  // React will find the nearest theme Provider and use its value.
  // In this case, the current theme value is "dark".
  static contextType = ThemeContext
  render() {
    return <Button theme={this.context} />}}Copy the code

Sibling communication

Sibling components mean they have a common parent!

Before we get to sibling components, we need to talk about the concept of state promotion

State promotion: In React, shared state is achieved by moving the required state from multiple components up to their nearest common parent. This is called a state boost.

A simple example

Here is an example to help you understand:

We’ll start with a component called BoilingVerdict that accepts Celsius as a prop and prints out if the temperature is enough to boil water.

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>
  }
  return <p>The water would not boil.</p>
}
Copy the code

Next, we create a component called Calculator. It renders a for the input temperature and stores its value in this.state.temperature.

In addition, it renders the BoilingVerdict component based on the current input value.

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.state = {temperature: ' '};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    return (
        <p>Enter temperature in Celsius:</p>
        <input
          value={temperature}
          onChange={e= > this.handleChange(e)} />
        <BoilingVerdict
          celsius={parseFloat(temperature)} />); }}Copy the code

Add a second input box

Now the new requirement is that we provide the Fahrenheit input box on the basis of the existing Celsius input box, and keep the data synchronization between the two input boxes.

We have detached the TemperatureInput component from the Calculator component and added a new scale prop to it, which can be either “c” or “f” :(for Celsius and Fahrenheit)

const scaleNames = {
  c: 'Celsius'.f: 'Fahrenheit',}class TemperatureInput extends React.Component {
  constructor(props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)
    this.state = { temperature: ' '}}handleChange(e) {
    this.setState({ temperature: e.target.value })
  }

  render() {
    const temperature = this.state.temperature
    const scale = this.props.scale
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature} onChange={this.handleChange} />
      </fieldset>)}}Copy the code

We can now modify the Calculator component to render two separate temperature input box components:

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>)}}Copy the code

We now have two input fields, but when you enter a temperature in one, the other doesn’t update. This contradicts what we want: we want to keep them in sync.

Also, we cannot display the render results of the BoilingVerdict component through the Calculator component. This is because the Calculator component doesn’t know what the current temperature is hidden in the TemperatureInput component.

State of ascension

So far, both TemperatureInput components have kept their data independently of each other in their own internal states.

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ' '};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    // ...
Copy the code

However, we want the values in the two input boxes to be synchronized with each other. When we update the value in the Celsius input box, the Fahrenheit input box should display the converted Fahrenheit temperature and vice versa.

In React, shared state is achieved by moving the states that need to be shared between multiple components up to their nearest common parent. This is known as a “state boost.” Next, we move state from the TemperatureInput component into the Calculator component.

If the Calculator component has a shared state, it becomes the “data source” for the current temperature in the two temperature input boxes. It keeps the values of the two temperature input boxes consistent with each other. Since the props for both TemperatureInput components come from a common parent component Calculator, the content in the two input boxes will always be the same.

Let’s see how this is done.

The core point is that the parent component passes state-changing functions as props to the child component.

We store the current input temperature and scale in state inside the component. This state is “promoted” from both input box components and will be used as a common “data source” for both input box components. This is the minimum representation of all the data we need to render the two input boxes.

Since the values in both input fields are computed from the same state, they are always in sync:

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: ' '.scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  tryConvert(temperature, convert){...// To convert the temperature
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;    				const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
  				onTemperatureChange={this.handleCelsiusChange} />
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
					onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>); }}Copy the code

Let’s take a look at how the TemperatureInput component changes. We remove the state of the component itself and read the temperature data by using this.props. Temperature instead of this.state.temperature. When we want the response data change, we need to call the Calculator component provides the enclosing props. OnTemperatureChange (), and no longer use this. SetState ().

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)}handleChange(e) {
    this.props.onTemperatureChange(e.target.value)
  }

  render() {
    const temperature = this.props.temperature
    const scale = this.props.scale
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature} onChange={this.handleChange} />
      </fieldset>)}}Copy the code

This.state. temperature and this.state.scale in the Calculator component will now be updated regardless of which input field you edit. One of the input boxes holds the user’s input and values, and the other always displays the transformed result based on that value.

Let’s take a look at what happens when you edit the content of the input field:

  • React calls DOM<input>onChangeMethods. In this case, it isTemperatureInputThe component’shandleChangeMethods.
  • TemperatureInputIn the componenthandleChangeMethod will callthis.props.onTemperatureChange()And pass in the newly entered value as a parameter. The props such asonTemperatureChangeAll from the parent componentCalculatorTo provide.
  • Subcomponent used for Celsius input during initial renderingTemperatureInputIn theonTemperatureChangeThe methods andCalculatorIn the componenthandleCelsiusChangeSame method, while, for the Fahrenheit input subcomponentTemperatureInputIn theonTemperatureChangeThe methods andCalculatorIn the componenthandleFahrenheitChangeSame method. Therefore, whatever input field is being edited will be calledCalculatorThe corresponding method in the component.
  • And within these methods,CalculatorComponent is called by using the new input value corresponding to the temperature measurement unit of the current input boxthis.setState()React is then asked to re-render itself.
  • The React callCalculatorThe component’srenderMethod to get a UI rendering of the component. The temperature conversion takes place at this point, and the values in the two input boxes are recalculated from the current input temperature and its units of measurement.
  • Use the ReactCalculatorThe new props provided by the component are called in two separate waysTemperatureInputThe child componentrenderMethod to get the child componentUIRendering.
  • ReactcallBoilingVerdictThe component’srenderMethod and place the Celsius value into the componentpropsMethod is passed in.
  • React DOMMatches whether the water is boiling based on the input value and updates the result toDOM. The input box we just edited receives its current value, and the other input box is updated with the converted temperature value.

Because each update goes through the same steps, the contents of the two input fields are always in sync.

Having said that, let’s now see how it applies to sibling communication!

Now here’s a scenario

  • Draw the login page: Enter the user name and password
  • Click login

class Login extends React.Component {
		constructor(props) {
        super (props);
        this.state = {
            userName:"".password:""}}handlerLogin(e){
      this.setState(e)
    }

    render(){
        return( <div> <UserNameInput onChange = {value => this.handlerLogin({username:value})}> <PasswordInput onChange = {value => this.handlerLogin({password:value})}> </div> ) } } class UserNameInput extends React.Component { handlerUserName(e){ this.props.handlerLogin(e.target.value); } render(){return (<div> <input onChange={e => this.handlerusername (e)} placeholder=" please input user name "/> </div>)}} class PasswordInput extends React.Component { handlerPassword(e){ this.props.handlerLogin(e.target.value); } render(){return (<div> <input onChange={e => this.handlerusername (e)} placeholder=" please input password "/> </div>)}}Copy the code

The code here is not completely written, but we can see that we can get the username and password in the App component, and then we can call the login interface from here.

Next day forecast

In the next section, we will introduce the knowledge about communication between React components. In the component-based content, we will revise the previous actual case and optimize the previous actual case plan. Stay tuned!