目录
1. 组件通讯
- 组件是独立封闭的单元
- 一个完整的页面通常是由多个组件组合而成的
- 组件之间经常需要共享一些数据,所以就需要组件之间能够通讯
1.1 props
- props 用于接收外界向组件中传递的数据
实现方法:
- 调用组件时,在组件标签中使用自定义属性向组件内部传递数据
- 组件内部可以使用 props 来接收自定义属性传递的数据
- 函数组件: props
- 类组件: this.props
注意事项:
- 除了字符串之外,其他类型的数据都需要使用 {}
- props 是只读属性,不能修改里面的数据
- 使用类组件时,如果写了构造函数,则需要将 props 传递给 super(),否则在 constructor 中无法使用props (其他方法可以使用)
<Hello
name="ls"
age={18}
hobby={['吃', '喝', '玩', '乐']}
fn={() => { console.log('fn函数被调用') }}
/>
// 当组建中自定义属性和值如上时, props 中数据的保存形式
this.props = {
name: 'ls',
age: 18,
hobby: ['吃', '喝', '玩', '乐'],
fn () => {
console.log('fn函数被调用')
}
}
目标: 使用函数组件和类组件接收组件自定义属性中的数据
类组件方式1:
类组件方式2:
如果写了构造函数,则需要将 props 传递给 super(),否则在 constructor 中无法使用props (其他方法可以使用)
函数组件:
1.2 父子组件嵌套
在一个组件中可以嵌套另一个组件,外层的组件就是父组件,内层的组件就是子组件
实现方法: 父组件的 render 方法中调用子组件
import React from 'react';
import ReactDOM from 'react-dom';
import './assets/css/fs.css'
// 创建子组件
class Son extends React.Component {
render () {
return (
<div className="son">我是子组件</div>
)
}
}
// 创建父组件
class Father extends React.Component {
render () {
return (
<div className="father">
<div>我是父组件</div>
{/* 父组件在render方法中渲染子组件 */}
<Son />
</div>
)
}
}
ReactDOM.render(<Father />, document.querySelector('#app'))
1.3 父向子传值
实现思路:
父组件渲染子组件时,将 state 中的数据添加到子组件的自定义属性中
实现要点:
- 父组件要有state,保存数据 (某些数据准备传给子组件)
- 父组件调用子组件时,为子组件添加自定义属性,将 state 中的数据设置为属性的值
- 子组件中使用props接收父组件数据
import React from 'react'
import ReactDOM from 'react-dom'
import './assets/css/fs.css'
// 目标: 子组件接收到父组件的数据
class Son extends React.Component {
render () {
return (
<div className="son">
我是子组件<br />
{/* 子组件使用 props 来接收父组件数据 */}
接收到父组件的数据为: {this.props.name} - {this.props.age}
</div>
)
}
}
class Father extends React.Component {
state = {
name: '二狗子',
age: 8
}
render () {
return (
<div className="father">
我是父组件
{/* 父组件在调用子组件时,将数据通过自定义属性传递给子组件 */}
<Son name={this.state.name} age={this.state.age} />
</div>
)
}
}
ReactDOM.render(<Father />, document.querySelector('#app'))
1.4 子向父传值 -1
核心: 利用回调函数
-
父组件
1)父组件中创建一个接收子组件数据的函数
2)父组件调用子组件时,将函数作为参数传给子组件
-
子组件 (子组件已经将父组件函数保存在 props 中)
1)子组件中使用 state 定义要传递给父组件的数据
2)子组件利用事件调用父组件传递的函数,并将数组作为参数传入
1.5 子向父传值 -2
核心: 子组件的render方法不再渲染任何内容,而是返回一个函数,将渲染的任务放在父组件的回调函数中
import React from 'react';
import ReactDOM from 'react-dom';
import './css/fs.css'
class Father extends React.Component {
// 父组件中使用回调函数接收子组件返回的数据,并将返回数据显示在页面上
getChildMsg = data => {
return <div>接收到子组件数据: {data}</div>
}
render () {
return (
<div className="fdiv">
父组件
<Son fn={this.getChildMsg} />
</div>
)
}
}
class Son extends React.Component {
state = {uname: '韦小宝'}
render () {
// 子组件返回函数
return this.props.fn(this.state.uname)
}
}
ReactDOM.render(<Father />, document.querySelector('#app'))
1.6 兄弟组件传值
思路: 子向父传值 、 父向子传值 两步的结合
-
子组件(Inp) 通过调用父组件(App)的函数将数据传递给父组件 (子向父传值)
-
父组件(App)再通过子组件(Screen)属性向子组件传值 (父向子传值)
核心名词: 状态提升
状态提升:将数据统一放在父组件中进行保存,父组件既要提供state保存数据,也要提供方法,让子组件能向父组件进行传值
import React from 'react'
import ReactDOM from 'react-dom'
import './assets/css/bro.css'
// Screen 组件用来显示内容
class Screen extends React.Component {
// this.props = {msg: '默认信息'}
render () {
return (
<div className="screen">
{this.props.msg}
</div>
)
}
}
// Inp 组件用来输入和发送内容
class Inp extends React.Component {
state = {
txt: ''
}
sendClick = () => {
// 将文本框中的数据发送给父组件
this.props.func(this.state.txt)
}
getTxt = e => {
this.setState({
txt: e.target.value
})
}
render () {
return (
<div>
<input type="text" className="inp" value={this.state.txt} onChange={this.getTxt} />
<button className="btn" onClick={this.sendClick}>发送</button>
</div>
)
}
}
// App 组件(父组件),整合 Screen 和 Inp (兄弟组件)
class App extends React.Component {
state = {
showMsg: '默认信息'
}
// 接收Inp子组件的state中的数据
getInpData = data => {
// console.log('data:', data)
// 将data中保存数据(txt),存放到state.showMsg中
this.setState({
showMsg: data
})
}
render () {
return (
<div className="bj">
<Screen msg={this.state.showMsg}/>
<Inp func={this.getInpData} />
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
1.7 Context
-
传统方式缺陷明显,一层一层传递太过麻烦
-
解决方案: 使用 Context 可以实现跨组件传值
-
实现步骤:
-
调用 React.createContext() 方法创建 Provider (提供数据) 和 Consumer (消费数据)
1)Provider 组件用来提供数据,将要传递的数据写入 Provider 的属性中
注意:传递数据时属性必须是 value
2)Consumer 组件内部可以使用回调函数的形式使用 Provider 提供的数据
-
使用 Provider 组件包裹最外层的父组件,并使用 value属性传递数据
-
使用 Consumer 接收数据
-
目标: 使用 context 将App中的数据传递到 SubChild 中
import React from 'react'
import ReactDOM from 'react-dom'
import './css/context.css'
//1. 调用 React.createContext() 方法创建 Provider 和 Consumer 两个组件
const {Provider, Consumer} = React.createContext()
class App extends React.Component {
state = {
msg: '我是 App.state.msg 的数组'
}
render () {
return (
//2. 使用 Provider 组件包裹整个组件, 使用 value 属性传递数据
<Provider value={this.state.msg}>
<div className="app">
App组件
<Child />
</div>
</Provider>
)
}
}
const Child = () => {
return (
<div className="child">
Child组件
<SubChild />
</div>
)
}
const SubChild = () => {
return (
<div className="subchild">SubChild组件:
{/* 3. 使用 Consumer 组件来接收 Provider 提供的数据 */}
{/* Consumer 中是一个函数,参数data中就保存了Provider中value提供的数据 */}
<Consumer>
{data => <span>{data}</span>}
</Consumer>
</div>
)
}
ReactDOM.render(<App />, document.querySelector('#app'))
2. props其他功能
2.1 children 属性
- render 方法在渲染组件标签时可以使用双标签
- 组件使用双标签时,内部可以填入子节点,例如:文本子节点、标签子节点、JSX子节点、函数子节点
- props.children 中以数组形式保存这些子节点
import React from 'react'
import ReactDOM from 'react-dom'
class Show extends React.Component {
render () {
return (
<div>我是show</div>
)
}
}
class App extends React.Component {
render () {
// App组件子节点的信息都保存在 this.props.children 属性中
console.log(this.props)
return (
<div>我是App组件</div>
)
}
}
ReactDOM.render(
<App>
<h1>我是写在App中的内容</h1>
<Show>哈哈哈</Show>
<div>
{
() => {
console.log('兄弟12345')
}
}
</div>
</App>,
document.querySelector('#app')
)
2.2 props校验
- props用来接收外来数据,这些数据可以在组件中被使用
- 但是组件的调用者可能会传入一些不符合规则的数据
- 当组件调用者传入不符合规则的数据时,用该进行错误提示
props校验的目标: 让组件的调用者清楚的知道,调用组件时产生错误的原因
解决方案: 使用 prop-types 第三方包
使用步骤:
-
安装 prop-types: npm i prop-types
-
导入 prop-types 包
-
为组件添加propTypes验证
//1. 导入 prop-types 包
import PropTypes from 'prop-types'
//2. 为组件添加验证
App.propTypes = {
color: PropTypes.array
}
2.3 常见校验规则
- 常见数据类型: array、bool、func、number、object、string
- React元素类型: element
- 必填项: isRequired
- 特定结构: shape({})
完整案例:https://react.docschina.org/docs/typechecking-with-proptypes.html
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
class App extends React.Component {
render () {
console.log(this.props)
return (
<div>哈哈</div>
)
}
}
// 属性 name 类型为字符串,必填
// 属性 age 类型为数值
// 属性 hobbies 类型为数组
// 属性 firends 类型为对象 {name: 'zs', age: 170}
App.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
hobbies: PropTypes.array,
firends: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number
})
}
ReactDOM.render(
<App name="罗小黑" age={18} hobbies={['吃饭', '睡觉']} firends={{name:'zs', age:20}} />,
document.querySelector('#app')
)
2.4 默认值
调用组件时可以为组件设置默认值
如果调用组件时设置了属性值则使用属性值,没有设置属性值则使用默认属性值
import React from 'react'
import ReactDOM from 'react-dom'
class Pagination extends React.Component {
render () {
return (
<div>
每页显示数量: {this.props.pagesize}
当前显示第{this.props.pagenum}页
</div>
)
}
}
// 设置默认值
Pagination.defaultProps = {
pagenum: 1,
pagesize: 10
}
ReactDOM.render(
<Pagination pagenum={3} />,
document.querySelector('#app')
)
3. 组件的生命周期
- 生命周期: 组件从创建到加载运行再到卸载的整个过程就是生命周期 (创建 --> 加载运行 --> 卸载)
- 掌握组件生命周期有助于我们理解组件的运行方式,完成更复杂的功能,分析解决错误
- 生命周期的每个阶段中,都有钩子函数,钩子函数能够实现更多的功能
- 只有类组件才有生命周期
3.1 创建阶段
创建时会顺序执行三个函数: constructor —> render —> componentDidMount
import React from 'react'
import ReactDOM from 'react-dom'
class Hello extends React.Component {
constructor () {
super()
console.log('执行了constructor 函数')
}
render () {
console.log('执行了 render 函数')
return (
<div id="title">Hello 组件</div>
)
}
componentDidMount () {
console.log('执行了 componentDidMount 函数')
const title = document.querySelector('#title')
console.log(title)
}
}
ReactDOM.render(<Hello />, document.querySelector('#app'))
执行结果:
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时 | 1. 初始化 state 2. 使用 bind 修改方法的this指向 |
render | 每次渲染组件时 | 渲染页面 (不能调用 setState 方法) |
componentDidMount | 渲染完成后 | 1. 发送网络请求 2. DOM操作 |
3.2 更新阶段
-
更新时会顺序执行两个函数: render —> componentDidUpdate
-
有三种情况会更新组件:
- forceUpdate(): 调用 forceUpdate() 强制进行更新
- setState(): 调用了setState 方法更新数据
- new props: props 的值发生改变时
1)调用 forceUpdate() 强制进行更新
2)调用了setState 方法更新数据
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
constructor () {
super()
this.state = {
name: '安琪拉',
level: 1,
hp: 680,
mp: 320
}
console.log('执行了constructor 函数')
}
handleClick = () => {
// 调用 forceUpdate 方法能够强制更新
// this.forceUpdate()
// 调用 setState 方法更新数据后触发更新
this.setState({
level: this.state.level + 1,
hp: this.state.hp + 1,
mp: this.state.mp + 1
})
}
render () {
console.log('执行了 render 函数')
return (
<div>
<ul>
<li>英雄: {this.state.name}</li>
<li>等级: {this.state.level}</li>
<li>血量: {this.state.mp}</li>
<li>蓝量: {this.state.hp}</li>
</ul>
<button onClick={this.handleClick}>升级</button>
</div>
)
}
componentDidMount () {
console.log('执行了 componentDidMount 函数')
}
componentDidUpdate () {
console.log('执行了 componentDidUpdate 函数')
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
3)props的值发生改变时 (更新父组件的state就是更新子组件的props)
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
constructor () {
super()
this.state = {
name: '安琪拉',
level: 1,
hp: 680,
mp: 320
}
console.log('App-constructor')
}
handleClick = () => {
// 调用 forceUpdate 方法能够强制更新
// this.forceUpdate()
// 调用 setState 方法更新数据后出发更新
this.setState({
level: this.state.level + 1,
hp: this.state.hp + 1,
mp: this.state.mp + 1
})
}
render () {
console.log('App-render')
return (
<div>
<Hero attr={this.state} />
<button onClick={this.handleClick}>升级</button>
</div>
)
}
componentDidMount () {
console.log('App-componentDidMount')
}
componentDidUpdate () {
console.log('App-componentDidUpdate')
}
}
class Hero extends React.Component {
constructor () {
super()
console.log('Hero-constructor')
}
render () {
console.log('Hero-render')
return (
<ul>
<li>英雄: {this.props.attr.name}</li>
<li>等级: {this.props.attr.level}</li>
<li>血量: {this.props.attr.mp}</li>
<li>蓝量: {this.props.attr.hp}</li>
</ul>
)
}
componentDidMount () {
console.log('Hero-componentDidMount')
}
componentDidUpdate () {
console.log('Hero-componentDidUpdate')
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
钩子函数 | 触发时机 | 作用 |
---|---|---|
render | 每次渲染 | 渲染页面 |
componentDidUpdate | 更新组件完成 | 1. 发送网络请求 2. DOM操作 注意: 如果要执行 setState(), 必须有结束条件 |
componentDidUpdate () {
console.log('执行了 componentDidUpdate 函数')
if (this.state.id === 10) {
return
}
this.setState({
id: 10
})
}
如果没有if判断,则会陷入死递归中
- setState 会调用 render 和 componentDidUpdate
- componentDidUpdate 又会再次调用 setState
3.3 卸载阶段
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 卸载组件 | 执行清理工作 |
import React from 'react'
import ReactDOM from 'react-dom'
// 目标: 点击按钮删除 Show 组件
class App extends React.Component {
state = {
flag: true
}
removeDOM = () => {
this.setState({
flag: false
})
}
render () {
return (
<div>
{
this.state.flag ? <Show /> : <div>show被删除了</div>
}
<button onClick={this.removeDOM}>删除</button>
</div>
)
}
}
class Show extends React.Component {
constructor () {
super()
this.timerId = setInterval(function () {
console.log(123)
}, 500)
}
render () {
return (
<div>Show 组件</div>
)
}
componentWillUnmount = () => {
console.log('Show-componentWillUnmount')
clearInterval(this.timerId)
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
3.4 不常用钩子函数(了解)
v16之前的钩子函数,现在已经被废弃:
componentWillMount、 componentWillReceiveProps、 componentWillUpdate
基本不用的钩子函数:getDerivedStaetFromProps、getSnapshotBeforeUpdate
4. 组件性能优化
4.1 组件更新机制
三种组件更新的情况:
- 调用 forceUpdate 方法强制更新
- 调用 setState 方法更新数据
- props 值发生变化时
问题:当组件进行嵌套时,更新了其中的某一个组件后,其他组件的更新方式?
当一个组件进行更新时,不但会更新自己,也会更新后代组件。但是不会更新父组件和兄弟组件
import React from 'react'
import ReactDOM from 'react-dom'
import './assets/css/update.css'
// 目标: 验证更新机制
class App extends React.Component {
updateApp = () => {
this.forceUpdate()
}
render () {
return (
<div className="app">
<div>
App组件 <button onClick={this.updateApp}>更新App组件</button>
</div>
<Left />
<Right />
</div>
)
}
componentDidUpdate () {
console.log('App-componentDidUpdate')
}
}
class Left extends React.Component {
updateApp = () => {
this.forceUpdate()
}
render () {
return (
<span className="left">
<div>
子组件--left
<button onClick={this.updateApp}>更新Left组件</button>
</div>
<Son1 />
</span>
)
}
componentDidUpdate () {
console.log('Left-componentDidUpdate')
}
}
class Right extends React.Component {
updateApp = () => {
this.forceUpdate()
}
render () {
return (
<span className="right">
<div>
子组件--right
<button onClick={this.updateApp}>更新Right组件</button>
</div>
<Son2 />
<Son3 />
</span>
)
}
componentDidUpdate () {
console.log('Right-componentDidUpdate')
}
}
class Son1 extends React.Component {
render () {
return (
<span className="son1">Son1组件</span>
)
}
componentDidUpdate () {
console.log('Son1-componentDidUpdate')
}
}
class Son2 extends React.Component {
render () {
return (
<span className="son2">Son2组件</span>
)
}
componentDidUpdate () {
console.log('Son2-componentDidUpdate')
}
}
class Son3 extends React.Component {
render () {
return (
<span className="son3">Son3组件</span>
)
}
componentDidUpdate () {
console.log('Son3-componentDidUpdate')
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
4.2 减轻 state
目标: 降低页面更新的频率 (只要state中数据发生改变,页面就会重载)
方式: 不用做渲染的数据,不要放在state中 (例如:定时器,直接挂在到this上即可)
class App extends React.Component {
// state中只保存要渲染到页面上的数据
state = {
userInfo: {
username: 'zs',
roleName: '超级管理员'
},
goodsList: [
{ id: 1, goodsName: '键盘', goodsPrice: 399},
{ id: 2, goodsName: '鼠标', goodsPrice: 199},
{ id: 3, goodsName: '耳机', goodsPrice: 699},
]
}
// 不用渲染到页面上的数据就直接挂在到 this 上即可
componentDidMount () {
this.timerId = setInterval(() => {}, 800)
}
componentWillUnmout () {
clearInterval(this.timerId)
}
}
4.3 避免不必要的渲染
组件更新机制:当一个组件进行更新时,不但会更新自己,也会更新后代组件。但是不会更新父组件和兄弟组件
存在的问题: 子组件没有变化,但是因为父组件发生了更新,所以子组件也要进行更新。这种更新是没有必要的,只会浪费系统性能
解决方案: 钩子函数 shouldComponentUpdate 用来判断组件是否需要更新
执行机制: 在执行 render 渲染页面前会先调用 shouldComponentUpdate 来判断是否需要进行渲染,如果返回值为true则进行渲染,反之则不渲染 (shouldComponentUpdate --> render)
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component{
state = {
count :0
}
shouldComponentUpdate (nextProps, nextState) {
// nextState 更新后的state的值
return true
}
handleClick = () => {
this.setState({
count:this.state.count+1
})
}
render() {
return (
<div>
{this.state.count}
<hr />
<button onClick={this.handleClick}>更新</button>
</div>
)
}
}
class Show extends React.Component{
render() {
return (
<div>
Show组件
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
4.3.1 state
在 shouldComponentUpdate 方法中可以使用两个数据
- nextState: state更新后的值
- this.state: 未更新之前的值
- 判断更新后的值和跟新前的值是否发生变化即可
import React from 'react'
import ReactDOM from 'react-dom'
// 目标: state 控制更新
// 每次点击按钮更新num中的数字
// 核心思想: 判断更新前后state中的num值是否相同,相同则不更新组件,不相同则更新
class App extends React.Component {
state = {
count: 0
}
shouldComponentUpdate (nextProps, nextState) {
console.log('nextState', nextState)
console.log('this.state', this.state)
// 判断更新后的state和更新前的state数据是否一致
// 不一致返回true执行更新,一致返回false不更新
return nextState.count !== this.state.count
}
updateApp = () => {
this.setState({
count: Math.floor(Math.random() * 3)
})
}
render () {
console.log('render')
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.updateApp}>更新组件</button>
</div>
)
}
componentDidUpdate () {
console.log('App-componentDidUpdate')
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
4.3.2 props
shouldComponentUpdate 方法中的另外两个数据
- nextProps: props 更新后的值
- this.props: props 未更新前的值
- 判断两次props中的值,来决定是否要更新
- 注意: 当子组件不渲染时,父组件也不要进行渲染
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
state = {
count: 0
}
// 判断父组件是否需要更新
shouldComponentUpdate (nextProps, nextState) {
return nextState.count !== this.state.count
}
updateApp = () => {
this.setState({
count: Math.floor(Math.random() * 3)
})
}
render () {
return (
<div>
<Show num={this.state.count} />
<button onClick={this.updateApp}>更新组件</button>
</div>
)
}
componentDidUpdate () {
console.log('App-componentDidUpdate')
}
}
class Show extends React.Component {
// 判断子组件是否需要更新
shouldComponentUpdate (nextProps) {
console.log('nextProps', nextProps)
console.log('this.props', this.props)
return nextProps.num !== this.props.num
}
render () {
return (
<div>{this.props.num}</div>
)
}
componentDidUpdate () {
console.log('Show-componentDidUpdate')
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
4.4 纯组件
纯组件: React.PureComponet 与 React.Component 功能相似,也可以用来实现类组件
作用: 纯组件内部自动实现了 shouldComponentUpdate 钩子函数
原理: 纯组件内部会自动比对前后两次 props 和 state 的值来决定是否需要重新渲染
class App extends React.PureComponent {
render () {
return (
<div>纯组件</div>
)
}
}
案例:使用纯组件完成随机数案例
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.PureComponent {
state = {
count: 0
}
updateApp = () => {
this.setState({
count: Math.floor(Math.random() * 3)
})
}
render () {
return (
<div>
<div>{this.state.count}</div>
<div><button onClick={this.updateApp}>随机更新</button></div>
</div>
)
}
componentDidUpdate () {
console.log('App-componentDidUpdate')
}
}
ReactDOM.render(<App />, document.querySelector('#app'))