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.
If there are more levels, it is very troublesome to transfer layer by layer, and the code is very redundant:
React
provides aAPI:Context
;Context
provides a way to share such values between components without having to explicitly passprops
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 currentcontext
valueProvider
- Only when the component tree does not match
Provider
, itsdefaultValue
parameter will take effect.defaultValue
that the component did not find the correspondingProvider
during the top-level search process, then the default value is used
Note: When passesundefined
to the value of Provider, thedefaultValue
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 avalue
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 theContext
- 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 aReact
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)} />
);
}
}
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>
)
}
}
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 thehandleChange
methodTemperatureInput
handleChange
method in theTemperatureInput
component will callthis.props.onTemperatureChange()
and pass in the newly entered value as a parameter. Its props such asonTemperatureChange
are provided by the parent componentCalculator
- When the first rendering, the input to the subassembly degrees
TemperatureInput
inonTemperatureChange
methodCalculator
assemblyhandleCelsiusChange
the same manner, and for the input of the subassembly FahrenheitTemperatureInput
inonTemperatureChange
methodCalculator
assemblyhandleFahrenheitChange
same manner. Therefore, no matter which input box is edited, the corresponding method in theCalculator
- Inside these methods,
Calculator
component by using the new input value and the current frame corresponding to the input temperature measurement unit to invokethis.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 theTemperatureInput
sub-componentsUI
presentation of the sub-components. React
calls therender
BoilingVerdict
component, and passes in the Celsius temperature value in the componentprops
.React DOM
matches whether the water is boiling according to the input value, and updates the result toDOM
. 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.
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
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!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。