8
头图

join us!

160e2adfbd5da1 " , to provide front-end developers with technical information and a series of basic articles. For a better user experience, please move to our official website novices (160e2adfbd61a1 https://xhs-rookies.com/ ) to learn and get the latest articles in time.

"Code tailor" , if you are interested in our article or want to make some suggestions, follow "Novices of Xiaohe Mountain" public account, contact us, you can also watch it on WeChat Our article. Every suggestion or approval is a great encouragement to us!

Preface

In this section, we will introduce React . In the previous section, we said that the communication between parent-child components can be props and callback functions. However, as the application becomes larger and larger, the way of using props and callback functions will change. It's very cumbersome, so is there a simple way to communicate between non-parent and child components?

This article will introduce you to the following:

  • Communication between cross-level components
  • Context
  • Brother component communication

Communication between cross-level components

Context

Context provides a method to transfer data between component trees props to each layer of components

Context usage scenarios

  • For some scenarios: For example, some data needs to be shared among multiple components (regional preference, UI theme, user login status, user information, etc.).
  • App at the top level and pass it down layer by layer, this is a redundant operation for some components that do not require data in the middle layer.

image.png

If there are more levels, it is very troublesome to transfer layer by layer, and the code is very redundant:

  • React provides a API:Context ;
  • Context provides a way to share such values between components without having to explicitly pass props layer by layer through the component tree;
  • Context 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)

Context object that needs to be shared:

  • If a component subscribes to Context , then this component will read the current context value Provider
  • Only when the component tree does not match Provider , its defaultValue parameter will take effect. defaultValue that the component did not find the corresponding Provider during the top-level search process, then the default value is used
Note: When passes undefined to the value of Provider, the defaultValue consumer component will not take effect.
Context.Provider
<MyContext.Provider value={/* 某个值 */}>

Each Context object will return a Provider React component, which allows consumer components to subscribe to context changes:

  • Provider receives a value attribute and passes it to the consumer component;
  • One Provider can correspond to multiple consumer components;
  • Multiple Provider can also be nested, the inner layer will cover the outer layer data;

When the value value of Provider changes, all the consumer components inside it will be re-rendered;

Class.contextType
class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context
    /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
  }
  componentDidUpdate() {
    let value = this.context
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context
    /* ... */
  }
  render() {
    let value = this.context
    /* 基于 MyContext 组件的值进行渲染 */
  }
}
MyClass.contextType = MyContext

Mount class on contextType will be re-assigned to a property by the React.createContext() created Context objects:

  • This allows you to use this.context to consume the value on the Context
  • You can access it in any life cycle, including the render function;
Context.Consumer
<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

Here, the React component can also be subscribed to the context change. This allows you to subscribe to context functional component.

  • Here need function as a child element ( function as child ) this approach;
  • This function receives the current context value and returns a React node;

Context use

For example, in the following code, we manually adjust the style of a button component through a "theme" property:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />
  }
}

function Toolbar(props) {
  // Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
  // 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
  // 因为必须将这个值层层传递所有组件。
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  )
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />
  }
}

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

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light')
class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    )
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  )
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext
  render() {
    return <Button theme={this.context} />
  }
}

Brother component communication

Sibling components means that they have a common parent component!

Before we talk about the brother components, we must first talk about a concept: status upgrade

state promotion : In React , the state that needs to be shared among multiple components is moved up to their nearest common parent component to achieve shared state. This is the so-called status promotion .

Simple example

Next, we will use an example to help everyone understand deeply:

We will start with a component BoilingVerdict , which accepts the temperature of celsius prop , and prints out the result of whether the temperature is enough to boil water based on this.

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

Next, we create a component Calculator <input> for input temperature and saves 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)} />
    );
  }
}

image.png

image.png

Add a second input box

The new requirement now is that on the basis of the existing Celsius temperature input box, we provide the Fahrenheit input box and keep the data in the two input boxes synchronized.

We first Calculator component from the TemperatureInput component, and then add a new scale prop to it, which can be "c" or "f" : (represents temperature) 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>
    )
  }
}

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

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

image.png

We now have two input boxes, but when you enter the temperature in one, the other will not be updated. This contradicts our requirements: we want to keep them in sync.

In addition, we cannot display the rendering result of the BoilingVerdict Calculator Because the Calculator component does not know what the current temperature is TemperatureInput

Status promotion

So far, the two TemperatureInput components have saved their data independently of each other in their internal state.

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;
    // ...

However, we hope that the values in the two input boxes can 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 , 060e2adfbd6b5e that needs to be shared among multiple components is state up to their nearest common parent component, and state can be shared. This is the so-called "status promotion". Next, we will TemperatureInput State moving components to Calculator components go.

If the Calculator component has the shared state , it will become the "data source" of the current temperature in the two temperature input boxes. It can make the values of the two temperature input boxes consistent with each other. Since the two TemperatureInput components props are from a common parent component Calculator , so the contents of two input boxes will remain the same.

Let us see how this is achieved.

core points of props passes the state change function as 060e2adfbd6bb2 to the child component.

We will temperature and scale state inside the component. This state is "lifted" from the two input box components, and it will be used as a common "data source" for the two input box components. This is the smallest representation of all the data we need in order to render the two input boxes.

Since the values in the two input boxes are calculated 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){
      ... //用来转化温度
  }

  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>
    );
  }
}

Let us look at TemperatureInput component changes. We remove the state component itself, and read the temperature data by using this.props.temperature instead of this.state.temperature When we want to respond to data changes, we need to call Calculator provided by the this.props.onTemperatureChange() component, instead of using 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>
    )
  }
}

Now whether you edit the contents of which input box, Calculator components this.state.temperature and this.state.scale will be updated. One of the input boxes retains the user's input and takes a value, and the other input box always displays the converted result based on this value.

Let us reorganize what happens when you edit the content of the input box:

  • React will call onChange method <input> in the DOM. In this example, it is the handleChange method TemperatureInput
  • handleChange method in the TemperatureInput component will call this.props.onTemperatureChange() and pass in the newly entered value as a parameter. Its props such as onTemperatureChange are provided by the parent component Calculator
  • When the first rendering, the input to the subassembly degrees TemperatureInput in onTemperatureChange method Calculator assembly handleCelsiusChange the same manner, and for the input of the subassembly Fahrenheit TemperatureInput in onTemperatureChange method Calculator assembly handleFahrenheitChange same manner. Therefore, no matter which input box is edited, the corresponding method in the Calculator
  • Inside these methods, Calculator component by using the new input value and the current frame corresponding to the input temperature measurement unit to invoke this.setState() further requests re-rendering itself React.
  • React calls render Calculator component to get the UI rendering of the component. The temperature conversion is carried out at this time, and the values in the two input boxes are recalculated based on the current input temperature and its unit of measurement.
  • React uses the new props provided by the Calculator render method of the TemperatureInput sub-components UI presentation of the sub-components.
  • React calls the render BoilingVerdict component, and passes in the Celsius temperature value in the component props .
  • React DOM matches whether the water is boiling according to the input value, and updates the result to DOM . The input box we just edited receives its current value, and the content of the other input box is updated with the converted temperature value.

Thanks to the same steps for each update, the contents of the two input boxes can always be kept in sync.

Monitoring State in React DevTools

Having finished the state promotion, let's now see how it can be used in the communication between brother components!

Now there is such a scene

  • Draw the login page: enter the user name and password
  • Click to Login

image.png

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="请输入用户名"/>
        </div>
      )
    }
}

class PasswordInput extends React.Component {
     handlerPassword(e){
       this.props.handlerLogin(e.target.value);
     }

    render(){
        return (
          <div>
            <input onChange={e => this.handlerUserName(e)} placeholder="请输入密码"/>
          </div>
        )
      }
}

In fact, the code here is not finished, but what we can see is that we can already App component, and then we can call the login interface here.

Preview of the next section

In the next section, we will talk about the use of React component communication related knowledge. The componentized content will revise the previous actual combat case and optimize the previous actual combat plan. Stay tuned!


小和山的菜鸟们
377 声望2.1k 粉丝

每日进步的菜鸟,分享前端学习手册,和有心学习前端技术的小伙伴们互相探讨,一同成长。