本文记录 React 的学习过程,内容为组合 Components。
本节内容是组件与组件之间的组合,例如用不同组件构成 DOM 树,以及给不同的组件传递数据或者调用不同组件的方法,还有不同组件的生命周期。
1. 创建父组件
我们还是继续在之前的 Box
组件上进行操作,首先创建一个 Boxes
组件,其中包含一系列 Box
组件。
在 components
目录中创建 boxes.jsx
:
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { Component } from 'react';
class Boxes extends Component { state = { } render() { return ( <h1>Boxes</h1> ); } } export default Boxes;
|
然后修改一下 index.js
:
1 2 3 4 5 6 7 8
| 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>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ...
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
:
1 2 3 4 5 6 7 8 9 10 11
| import React, { Component } from 'react';
class Box extends Component { state = { x: this.props.x, };
... } export default Box;
|
3. 传递子节点
可以将标签写成 <Box></Box>
的形式,然后在标签中添加子标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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
组件的最上方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import React, { Component } from 'react';
class Box extends Component { 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() { console.log(this.props); return ( <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
只能在组件内部修改,不能在其他组件内修改)。
我们可以在父元素中定义好函数,然后将函数传给子元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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) => { 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;
|
这样子元素就能调用函数对父元素进行操作了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React, { Component } from 'react';
class Box extends Component { ...
render() { console.log(this.props); return ( <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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 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
的函数转移过来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| 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) => { 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}); }
handleClickLeft = (box) => { const boxes = [...this.state.boxes]; const k = boxes.indexOf(box); boxes[k] = {...boxes[k]}; 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import React, { Component } from 'react';
class Box extends Component { render() { return ( <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. 兄弟组件间传递消息
如果组件的结构关系更为复杂,那么就需要将多个组件共用的数据存放到最近公共祖先的 this.state
中。
我们创建一个 App
组件,其包含两个子组件 NavBar
(导航栏)和 Boxes
,这两个组件为兄弟组件。
首先是 navbar.jsx
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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;
|
然后是 app.jsx
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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;
|
现在假设我们要在 NavBar
中存放 Boxes
中有几个 Box
的信息,那么只能把信息放到这两个组件的最近公共祖先 App
中。
我们将 Boxes
中与 state
有关的内容都移到 App
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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;
|
移动后的 App
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| 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) => { 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}); }
handleClickLeft = (box) => { const boxes = [...this.state.boxes]; const k = boxes.indexOf(box); boxes[k] = {...boxes[k]}; 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;
|
现在即可在 NavBar
中读取 Boxes
的长度信息了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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. 无状态函数组件
当组件中没有用到 this.state
时,可以简写为无状态的函数组件。类相对于函数最大的好处就是可以很方便地维护状态(局部变量)。
无状态函数组件(Stateless Funtion Component),输入 sfc
即可自动补全出来。函数组件相当于只有 render
函数的类组件。注意:函数的传入参数为 props
对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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. 组件的生命周期
Mount
周期(挂载,表示对象被创建出来),执行顺序(按顺序执行三个函数):constructor() -> render() -> componentDidMount()
Update
周期(修改,例如点击按钮),执行顺序:render() -> componentDidUpdate()
Unmount
周期(删除),执行顺序:componentWillUnmount()
其中,componentDidUpdate
函数有两个参数,分别表示更新前的 props
和 state
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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;
|
输出的 state
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| {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
|
上一章:Web学习笔记-React(配置环境、ES6语法补充、Components)。
下一章:Web学习笔记-React(路由)。