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