The content of the notes is reproduced from AcWing's Web Application Course handout, course link: AcWing Web Application Course .
CONTENTS
The content of this section is the combination of components, such as using different components to form a DOM tree, passing data to different components or calling methods of different components, and the life cycles of different components.
1. Create a parent component
Box
We continue to work on the previous component, first create a Boxes
component, which contains a series Box
of components.
Create in components
directory boxes.jsx
:
import React, {
Component } from 'react';
class Boxes extends Component {
state = {
}
render() {
return (
<h1>Boxes</h1>
);
}
}
export default Boxes;
Then modify it index.js
:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import Boxes from './components/boxes';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Boxes />);
Now we Boxes
add multiple elements to Box
. When a component contains multiple parallel elements, we need to enclose them with a tag. You can use a virtual tag in React <React.Fragment>
:
import React, {
Component } from 'react';
import Box from './box';
class Boxes extends Component {
state = {
}
render() {
return (
<React.Fragment>
<Box />
<Box />
<Box />
</React.Fragment>
);
}
}
export default Boxes;
For convenience, it can also be represented by an array, and Box
the information of is stored in state
. Since the React component has several sons, their key
needs are different, so a unique one needs to be stored id
:
import React, {
Component } from 'react';
import Box from './box';
class Boxes extends Component {
state = {
boxes: [
{
id: 1, x: 0},
{
id: 2, x: 0},
{
id: 3, x: 0},
{
id: 4, x: 0},
]
}
render() {
return (
<React.Fragment>
{
this.state.boxes.map(box => (
<Box key={
box.id} />
))}
</React.Fragment>
);
}
}
export default Boxes;
2. Pass data from top to bottom
this.props
Data can be passed from top to bottom through attributes. For example we Boxes
pass in x
:
...
class Boxes extends Component {
state = {
...
}
render() {
return (
<React.Fragment>
{
this.state.boxes.map(box => (
<Box key={
box.id} x={
box.x} name='yyj' />
))}
</React.Fragment>
);
}
}
export default Boxes;
Box
Information can be output in console.log(this.props);
View content:
Box
Under modification x
:
import React, {
Component } from 'react'; // 输入imrc即可补全
class Box extends Component {
// 输入cc即可补全
state = {
x: this.props.x,
};
...
}
export default Box;
3. Pass child nodes
You can write tags in <Box></Box>
the form of and then add subtags to the tag:
import React, {
Component } from 'react';
import Box from './box';
class Boxes extends Component {
state = {
boxes: [
{
id: 1, x: 0},
{
id: 2, x: 0},
{
id: 3, x: 0},
{
id: 4, x: 0},
]
}
render() {
return (
<React.Fragment>
{
this.state.boxes.map(box => (
<Box key={
box.id} x={
box.x} name='yyj'>
<h1>Title</h1>
</Box>
))}
</React.Fragment>
);
}
}
export default Boxes;
In this way, this.props
there will be one more attribute children
, and you can use []
to access a sub-tag individually. We can define this passed value anywhere, for example, it can be placed at Box
the top of each component:
import React, {
Component } from 'react'; // 输入imrc即可补全
class Box extends Component {
// 输入cc即可补全
state = {
x: this.props.x,
};
handleClickLeft = (step) => {
this.setState({
x: this.state.x - step
});
}
handleClickRight = (step) => {
this.setState({
x: this.state.x + step
});
}
handleClickLeftTmp = () => {
this.handleClickLeft(10);
}
render() {
// Component类的函数,用来返回当前组件最后渲染的HTML结构是什么
console.log(this.props);
return (
// HTML标签中可以使用{}写一个表达式
<React.Fragment>
{
this.props.children}
<div style={
this.getStyles()}>{
this.state.x}</div>
<button onClick={
this.handleClickLeftTmp} className='btn btn-primary m-2'>Left</button>
<button onClick={
() => this.handleClickRight(10)} className='btn btn-success m-2'>Right</button>
</React.Fragment>
);
}
getStyles() {
...
}
}
export default Box;
4. Call functions from bottom to top
Parent elements can this.props
pass information to child elements through , and child elements can also use functions to pass information to parent elements. Suppose we need to delete someone by clicking the delete button Box
, and its information is stored in Boxes
, state
but the event triggered by our click is in Box
(note: each component's this.state
can only be modified within the component, not other components).
We can define the function in the parent element and then pass the function to the child element:
import React, {
Component } from 'react';
import Box from './box';
class Boxes extends Component {
state = {
boxes: [
{
id: 1, x: 0},
{
id: 2, x: 0},
{
id: 3, x: 0},
{
id: 4, x: 0},
]
}
handleDelete = (boxId) => {
// 遍历一遍state.boxes,将box.id不为传入的参数boxId的数据保留下来
const res = this.state.boxes.filter(box => box.id !== boxId);
this.setState({
boxes: res});
}
render() {
return (
<React.Fragment>
{
this.state.boxes.map(box => (
<Box key={
box.id} id={
box.id} x={
box.x} name='yyj'
onDelete={
this.handleDelete}
/>
))}
</React.Fragment>
);
}
}
export default Boxes;
In this way, the child element can call the function to operate on the parent element:
import React, {
Component } from 'react'; // 输入imrc即可补全
class Box extends Component {
// 输入cc即可补全
...
render() {
// Component类的函数,用来返回当前组件最后渲染的HTML结构是什么
console.log(this.props);
return (
// HTML标签中可以使用{}写一个表达式
<React.Fragment>
...
<button onClick={
() => this.props.onDelete(this.props.id)} className='btn btn-danger m-2'>Delete</button>
</React.Fragment>
);
}
getStyles() {
...
}
}
export default Box;
Now we Boxes
implement a Reset button in to clear Box
all x
:
import React, {
Component } from 'react';
import Box from './box';
class Boxes extends Component {
state = {
boxes: [
{
id: 1, x: 0},
{
id: 2, x: 1},
{
id: 3, x: 2},
{
id: 4, x: 3},
]
}
handleDelete = (boxId) => {
...
}
handleReset = () => {
const res = this.state.boxes.map(box => {
return {
id: box.id,
x: 0,
}
});
this.setState({
boxes: res});
}
render() {
console.log(this.state.boxes);
return (
<React.Fragment>
<button
onClick={
this.handleReset}
style={
{
marginBottom: '15px'}}
className='btn btn-info'
>Reset</button>
{
this.state.boxes.map(box => (
<Box key={
box.id} id={
box.id} x={
box.x} name='yyj'
onDelete={
this.handleDelete}
/>
))}
</React.Fragment>
);
}
}
export default Boxes;
When observing on the console, you can find that after clicking the Reset button x
, the is indeed set to zero, but Box
the displayed x
has not changed. This is because state
the value cannot be modified externally, so we can delete Box
the in state
and need to render the external in the component. state
value.
Each maintained data can only be saved in one this.state
. Do not modify the value of directly this.state
because setState
the function may overwrite the modification.
Modify Boxes
and transfer the functions Box
used in the previous operations state
:
import React, {
Component } from 'react';
import Box from './box';
class Boxes extends Component {
state = {
boxes: [
{
id: 1, x: 0},
{
id: 2, x: 1},
{
id: 3, x: 2},
{
id: 4, x: 3},
]
}
handleDelete = (boxId) => {
// 遍历一遍state.boxes,将box.id不为传入的参数boxId的数据保留下来
const res = this.state.boxes.filter(box => box.id !== boxId);
this.setState({
boxes: res});
}
handleReset = () => {
const res = this.state.boxes.map(box => {
return {
id: box.id,
x: 0,
}
});
this.setState({
boxes: res});
}
// 需要知道修改的是哪个box
handleClickLeft = (box) => {
const boxes = [...this.state.boxes]; // 浅拷贝一份
const k = boxes.indexOf(box); // 传入的box是引用,找出其在boxes中的下标k
boxes[k] = {
...boxes[k]}; // 再clone一遍,相当于创建新的state,深拷贝
boxes[k].x--;
this.setState({
boxes: boxes});
}
handleClickRight = (box) => {
const boxes = [...this.state.boxes];
const k = boxes.indexOf(box);
boxes[k] = {
...boxes[k]};
boxes[k].x++;
this.setState({
boxes: boxes});
}
render() {
return (
<React.Fragment>
<button
onClick={
this.handleReset}
style={
{
marginBottom: '15px'}}
className='btn btn-info'
>Reset</button>
{
this.state.boxes.map(box => (
<Box key={
box.id} id={
box.id} x={
box.x} name='yyj'
onDelete={
this.handleDelete}
onClickLeft={
() => this.handleClickLeft(box)}
onClickRight={
() => this.handleClickRight(box)}
/>
))}
</React.Fragment>
);
}
}
export default Boxes;
Then modify it Box
and this.state
replace it with the one passed by the parent component props
:
import React, {
Component } from 'react'; // 输入imrc即可补全
class Box extends Component {
// 输入cc即可补全
render() {
// Component类的函数,用来返回当前组件最后渲染的HTML结构是什么
return (
// HTML标签中可以使用{}写一个表达式
<React.Fragment>
<div style={
this.getStyles()}>{
this.props.x}</div>
<button onClick={
this.props.onClickLeft} className='btn btn-primary m-2'>Left</button>
<button onClick={
this.props.onClickRight} className='btn btn-success m-2'>Right</button>
<button onClick={
() => this.props.onDelete(this.props.id)} className='btn btn-danger m-2'>Delete</button>
</React.Fragment>
);
}
getStyles() {
let styles = {
width: '50px',
height: '50px',
backgroundColor: 'lightblue',
color: 'white',
textAlign: 'center',
lineHeight: '50px',
borderRadius: '5px',
position: 'relative',
left: this.props.x
};
if (this.props.x === 0) {
styles.backgroundColor = 'orange';
}
return styles;
}
}
export default Box;
5. Pass messages between sibling components
If the structural relationship of components is more complex, then data shared by multiple components needs to be stored in the nearest common ancestorthis.state
.
We create a App
component that contains two sub-components NavBar
(navigation bar) and Boxes
, these two components are sibling components.
The first is navbar.jsx
:
import React, {
Component } from 'react';
class NavBar extends Component {
state = {
}
render() {
return (
<nav className="navbar bg-body-tertiary">
<div className="container-fluid">
<a className="navbar-brand" href="/">Navbar</a>
</div>
</nav>
);
}
}
export default NavBar;
And then app.jsx
:
import React, {
Component } from 'react';
import NavBar from './navbar';
import Boxes from './boxes';
class App extends Component {
state = {
}
render() {
return (
<React.Fragment>
<div className='container'>
<NavBar />
<Boxes />
</div>
</React.Fragment>
);
}
}
export default App;
Now suppose we want to NavBar
store information Boxes
about several in Box
, then the information can only be placed App
in the most recent common ancestor of these two components.
We will move all the content related to Boxes
in to:state
App
import React, {
Component } from 'react';
import Box from './box';
class Boxes extends Component {
render() {
return (
<React.Fragment>
<button
onClick={
this.props.onReset}
style={
{
marginBottom: '15px'}}
className='btn btn-info'
>Reset</button>
{
this.props.boxes.map(box => (
<Box key={
box.id} id={
box.id} x={
box.x} name='yyj'
onDelete={
this.props.onDelete}
onClickLeft={
() => this.props.onClickLeft(box)}
onClickRight={
() => this.props.onClickRight(box)}
/>
))}
</React.Fragment>
);
}
}
export default Boxes;
After moving App
it is as follows:
import React, {
Component } from 'react';
import NavBar from './navbar';
import Boxes from './boxes';
class App extends Component {
state = {
boxes: [
{
id: 1, x: 0},
{
id: 2, x: 1},
{
id: 3, x: 2},
{
id: 4, x: 3},
]
}
handleDelete = (boxId) => {
// 遍历一遍state.boxes,将box.id不为传入的参数boxId的数据保留下来
const res = this.state.boxes.filter(box => box.id !== boxId);
this.setState({
boxes: res});
}
handleReset = () => {
const res = this.state.boxes.map(box => {
return {
id: box.id,
x: 0,
}
});
this.setState({
boxes: res});
}
// 需要知道修改的是哪个box
handleClickLeft = (box) => {
const boxes = [...this.state.boxes]; // 浅拷贝一份
const k = boxes.indexOf(box); // 传入的box是引用,找出其在boxes中的下标k
boxes[k] = {
...boxes[k]}; // 再clone一遍,相当于创建新的state,深拷贝
boxes[k].x--;
this.setState({
boxes: boxes});
}
handleClickRight = (box) => {
const boxes = [...this.state.boxes];
const k = boxes.indexOf(box);
boxes[k] = {
...boxes[k]};
boxes[k].x++;
this.setState({
boxes: boxes});
}
render() {
return (
<React.Fragment>
<div className='container'>
<NavBar
boxesCount={
this.state.boxes.length} // 将长度传给NavBar
/>
<Boxes
boxes={
this.state.boxes}
onReset={
this.handleReset}
onClickLeft={
this.handleClickLeft}
onClickRight={
this.handleClickRight}
onDelete={
this.handleDelete}
/>
</div>
</React.Fragment>
);
}
}
export default App;
The length information of can now NavBar
be read in :Boxes
import React, {
Component } from 'react';
class NavBar extends Component {
state = {
}
render() {
return (
<nav className="navbar bg-body-tertiary">
<div className="container-fluid">
<a className="navbar-brand" href="/">
Navbar <span>Boxes Count: {
this.props.boxesCount}</span>
</a>
</div>
</nav>
);
}
}
export default NavBar;
6. Stateless function components
When is not used in a component this.state
, it can be abbreviated as a stateless function component . The biggest advantage of classes over functions is that they can easily maintain state (local variables).
Stateless Function Component, input sfc
can be automatically completed. Function components are equivalent to render
class components with only functions. Note: The incoming parameters of the function are props
objects:
import React from 'react';
const NavBar = (props) => {
return (
<nav className="navbar bg-body-tertiary">
<div className="container-fluid">
<a className="navbar-brand" href="/">
Navbar <span>Boxes Count: {
props.boxesCount}</span>
</a>
</div>
</nav>
);
}
export default NavBar;
7. Component life cycle
Mount
Cycle (mounting, indicating that the object is created), execution sequence (execute three functions in sequence):constructor() -> render() -> componentDidMount()
Update
Cycle (modification, such as clicking a button), execution sequence:render() -> componentDidUpdate()
Unmount
Cycle (delete), execution order:componentWillUnmount()
Among them, the function has two parameters, representing the and componentDidUpdate
before update respectively :props
state
import React, {
Component } from 'react';
import NavBar from './navbar';
import Boxes from './boxes';
class App extends Component {
state = {
boxes: [
{
id: 1, x: 0},
{
id: 2, x: 1},
{
id: 3, x: 2},
{
id: 4, x: 3},
]
}
componentDidUpdate(prevProps, prevState) {
console.log('App - Update');
console.log(prevProps, this.props);
console.log(prevState, this.state);
}
...
render() {
console.log('App - Render');
return (
...
);
}
}
export default App;
The output state
content is as follows:
{boxes: Array(4)}
boxes: Array(4)
0: {id: 1, x: 0}
1: {id: 2, x: 1}
2: {id: 3, x: 2}
3: {id: 4, x: 3}
length: 4
[[Prototype]]: Array(0)
[[Prototype]]: Object
{boxes: Array(4)}
boxes: Array(4)
0: {id: 1, x: 1} (此处有区别)
1: {id: 2, x: 1}
2: {id: 3, x: 2}
3: {id: 4, x: 3}
length: 4
[[Prototype]]: Array(0)
[[Prototype]]: Object