Use Context to communicate across hierarchies

With the component-based web development approach, communication between components is an unavoidable topic. There are many ways for components to communicate with each other, so let’s introduce a way to use Context to communicate across hierarchies.

First of all, we usually start in a separate file, such as context.js. Define the following variables:

import React from 'react';
export const ThemeContext = React.createContext();
export const ThemeProvider = ThemeContext.Provider;
export const ThemeConsumer = ThemeContext.Consumer;
Copy the code

Then, in the ancestor component, the Provider object is used to pass values to descendant components.

 <ThemeProvider value={this.state.theme}>
    <HomePage />
    <UsersPage />
</ThemeProvider>
Copy the code

Note that if you pass the Provider’s value as a literal, React will re-render its child components each time, because two literal objects are always unequal in JavaScript.

{themeColor: 'red'}! = = {themeColor: 'red'}
Copy the code

In the Class child, you can get the Provider’s value in this way. We can use the Consumer object to get the value provided by the Provider:

<ThemeConsumer>
    {theme= ><h1 style={{color: theme.themeColor}} >Your Majesty asked me to patrol the mountains</h1>}
</ThemeConsumer>
Copy the code

First define the static contextType property in the Class.

static contextType = ThemeContext;
Copy the code

You can then get the value of the Provider as follows:

render() {
    const { themeColor } = this.context;
    return <div>
        <h1 style={{color: themeColor}} >Your Majesty asked me to patrol the mountains</h1>
    </div>
}
Copy the code

In a function component, you can use the useContext method to retrieve values passed down from the ancestor component. The following code:

export function UsersPage(props) {
    const {themeColor} = useContext(ThemeContext);
    return (
        <div style={{color:themeColor}}>I'm coming to your house!</div>
    );
};
Copy the code

High order component

React a high-order component is a function that receives one component and returns another.

const foo = Cmp= > props= > {
    return (
        <div className="border">
            <Cmp {. props} / >
        </div>
    );
};
Copy the code

In development, it is common to use advanced components to property broker an existing component (adding new properties) or wrap an existing component to return a new component type.

We can use higher-order components just like we call normal functions, or we can use the decorator syntax in ES7.

@foo
class Child extends Component {
    render() {
        return (
            <div>HOC {this.props.name}</div>); }}Copy the code

Recursive components

Recursive components are typically used to render recursive data, such as nodes in the following code.

this.nodes = [
  {
    val: "v".children: [{val: "v2" },
      { val: "v3" },
      { val: "v4".children: [{ val: "vv2" }, { val: "vv3"}]},],}, {val: "t".children: [{val: "t2" },
      { val: "t3" },
      { val: "t4".children: [{ val: "tt2" }, { val: "tt3"}]},],},];Copy the code

To implement a recursive component, simply call itself as if it were a recursive function, as shown in the code below!

class Node extends Component {
  render() {
    let { val, children } = this.props.node;
    return (
      <div>
        <h1>{val}</h1>
        <div>{children && children.map((node) => <Node node={node} />)}</div>
      </div>); }}Copy the code

Mock Antd4 form components

Those of you who are familiar with Antd4 should be familiar with the use of its forms. As shown in the following code:

<Form
  form={form}
  onFinish={(values) = > {
    console.log("onFinish......", values);
  }}
  onFinishFailed={() = > {
    console.log("onFinishFailed......");
  }}
>
  <Field name="username">
    <input placeholder="That's great." />
  </Field>
  <Field name="password">
    <input placeholder="That's great." />
  </Field>
  <button type="submit">submit</button>
</Form>
Copy the code

To do this, you typically use the Context object, passing down an instance of the FormStore class in the Form component.

Context is defined as follows:

import { createContext } from 'react';
const FieldContext = createContext()
const FieldProvider = FieldContext.Provider
const FieldConsumer = FieldContext.Consumer
Copy the code

The Form component is defined as follows:

import { FieldProvider } from "./FieldContext";
export default function Form({ children, form, onFinish, onFinishFailed }) {
  form.setCallback({
      onFinish,
      onFinishFailed,
  })
  return (
    <form
      onSubmit={(event)= > {
        event.preventDefault();
        form.submit();
      }}
    >
      <FieldProvider value={form}>{children}</FieldProvider>
    </form>
  );
}
Copy the code

The FormStore type is defined as follows:

import React from 'react';
class FormStore{
    constructor(){
        this.store = {}
        this.fieldEntities = []
        this.callbacks = {}
    }
    registerField = entity= > {
        this.fieldEntities.push(entity)
        return () = >{
            this.fieldEntities = this.fieldEntities.filter(item= >item ! == entity)delete this.store[entity.props.name]
        }
    }
    getFieldValue = name= > {
        return this.store[name];
    }
    setFieldsValue = newStore= > {
        this.store = { ... this.store, ... newStore, }this.fieldEntities.forEach(entity= > {
            let {name} = entity.props;            
            Object.keys(newStore).forEach(key= > {
                if(name === key){
                    entity.onStoreChange();
                }
            
            })
        })
    }

    submit = () = > {
        this.callbacks['onFinish'] (this.store);
    }

    setCallback = (callbacks) = > {
        this.callbacks = {
            ... callbacks,
            ... this.callbacks
        }
    }

    getForm = () = > {
        return {
            submit: this.submit,
            registerField: this.registerField,
            getFieldValue: this.getFieldValue,
            setFieldsValue: this.setFieldsValue
        }
    }
}

export default function useForm(){
    const formRef = React.useRef();
    if(! formRef.current){const formstore = new FormStore();
        formRef.current = formstore;
    }
    return [formRef.current];
}
Copy the code

Field is defined as follows:

import React, { Component } from "react";
import { FieldContext } from "./FieldContext";
export default class Field extends Component {
  static contextType = FieldContext;

  componentDidMount() {
    const { registerField } = this.context;
    this.unregisterField = registerField(this);
  }

  componentWillUnmount() {
    if (this.unregisterField) {
      this.unregisterField();
    }
  }

  onStoreChange = () = > {
    this.forceUpdate();
  };

  getControlled = () = > {
    const { name } = this.props;
    const { getFieldValue, setFieldsValue } = this.context;
    return {
      value: getFieldValue(name) || "".onChange: (event) = > {
        const newValue = event.target.value;
        // console.log("newValue", newValue);setFieldsValue({ [name]: newValue }); }}; };render() {
    const { children } = this.props;
    const returnChildNode = React.cloneElement(children, this.getControlled());
    returnreturnChildNode; }}Copy the code