笔记内容转载自 AcWing 的 Web 应用课讲义,课程链接:AcWing Web 应用课。
CONTENTS
- 1. 创建父组件
- 2. 从上往下传递数据
- 3. 传递子节点
- 4. 从下往上调用函数
本节内容是组件与组件之间的组合,例如用不同组件构成 DOM 树,以及给不同的组件传递数据或者调用不同组件的方法,还有不同组件的生命周期。
1. 创建父组件
我们还是继续在之前的 Box
组件上进行操作,首先创建一个 Boxes
组件,其中包含一系列 Box
组件。
在 components
目录中创建 boxes.jsx
:
import React, { Component } from 'react';
class Boxes extends Component {
state = { }
render() {
return (
<h1>Boxes</h1>
);
}
}
export default Boxes;
然后修改一下 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 />);
现在我们在 Boxes
中加入多个 Box
,当一个组件中包含多个并列元素的时候,需要用一个标签将他们括起来,可以使用 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;
为了方便也可以用一个数组来表示,将 Box
的信息存到 state
里,由于 React 组件如果有若干个儿子那么他们的 key
需要不一样,因此还需要存一个唯一的 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. 从上往下传递数据
通过 this.props
属性可以从上到下传递数据。例如我们在 Boxes
中传递 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
中输出信息 console.log(this.props);
查看内容:
修改 Box
中的 x
:
import React, { Component } from 'react'; // 输入imrc即可补全
class Box extends Component { // 输入cc即可补全
state = {
x: this.props.x,
};
...
}
export default Box;
3. 传递子节点
可以将标签写成 <Box></Box>
的形式,然后在标签中添加子标签:
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;
这样 this.props
中会多一个属性 children
,可以使用 []
单独访问某个子标签。我们可以将这个传过来的值定义在任何地方,例如可以放到每个 Box
组件的最上方:
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. 从下往上调用函数
父元素可以通过 this.props
向子元素传递信息,子元素也可以使用函数向父元素传递信息。假设我们需要实现通过点击删除按钮删除某个 Box
,其信息保存在 Boxes
的 state
中,但是我们点击触发事件是在 Box
中(注意:每个组件的 this.state
只能在组件内部修改,不能在其他组件内修改)。
我们可以在父元素中定义好函数,然后将函数传给子元素:
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;
这样子元素就能调用函数对父元素进行操作了:
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;
现在我们在 Boxes
中实现一个 Reset 按钮实现清空所有 Box
的 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;
在控制台观察时可以发现点击 Reset 按钮后 x
确实置零了,但是 Box
显示出来的 x
并没有改变,这是因为 state
值不能在外部修改,因此我们可以将 Box
中的 state
删掉,需要在该组件中渲染外面的 state
的值。
每个维护的数据仅能保存在一个 this.state
中,不要直接修改 this.state
的值,因为 setState
函数可能会将修改覆盖掉。
修改 Boxes
,将之前 Box
中操作 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;
然后修改 Box
,将 this.state
替换成父组件传递过来的 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;