第四节: React组件通讯
1, React组件通讯介绍
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据,一个完整的功能拆分成多个组件,而在这个过程中,多个组件之间不可避免的要共享数据,为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯。
2,组件的props
- 组件是封闭的,要接受外部的数据应该通过props接收
- props的作用:接收传递给组件的数据
- 传递数据: 给组件标签添加属性
- 接收数据: 函数组件通过参数props接收数据,类组件通过this.props接收
<Hello name="java" age={10} />
function Hello(props) {
console.log(props)
return (
<div>{props.name}</div>
)
}
class Hello extends React.Component {
render() {
return (
<div>接收到的数据:{ this.props.age }</div>
)
}
}
<Hello name="java" age={19} />
(1)可以给组件传递任意类型的数据
特点
ReactDOM.render(
<Hello2
name="zlm"
age={19}
colors={['red', 'green', 'blue']}
fn={() => console.log('这是一个函数')}
tag={<p>这是一个P标签</p>}
/>,
document.getElementById('root')
);
(2)props是只读的对象,只能读取属性的值,无法修改对象
(3)注意:使用类组件时,如果写了构造函数,应该将props传递给super(), 否则,无法在构造函数中获取到props, 其实在render()中是可以拿到this.props值的
推荐写法:直接在construtor中传递props \
class Props extends React.Component {
constructor(props) {
super(props) // 推荐将props传递给父类构造函数
}
render() {
return <div>{this.props.age}</div>
}
}
3, 组件通讯的三种方式
一,父组件传递数据给子组件
1,父组件提供要传递的state数据
2,给子组件标签添加属性,值为state中的数据
3,子组件中通过props接收父组件中传递的数据
class Father extends React.Component {
state = { lastName: 'zlm' }
render() {
return (
<div>
传递给子组件:<Child name={ this.state.lastName } />
</div>
)
}
}
function Child(props) {
return (
<div>
子组件接收到的数据{ props.name }
</div>
)
}
思路:利用回调函数,父组件提供回调函数,子组件调用,将要传递的数据作为回调函数的参数
二,子组件传递数据给父组件 1, 父组件提供一个回到函数(用于接收数据)
import React from 'react'
class Father extends React.Component {
state = { lastName: 'zlm' }
// 回调函数,用了接收数据
getChildMsg = (data) => {
console.log('接收到子组件中传递过来的数据', data)
this.setState({
lastName: data
})
}
render() {
return (
<div>
父组件:{this.state.lastName}
<Child name={ this.state.lastName } getMsg={this.getChildMsg} />
</div>
)
}
}
// 子组件
class Child extends React.Component {
state = { msg: '书都有'}
handleClick = () => {
this.props.getMsg(this.state.msg)
}
render() {
return (
<div>
子组件: <button onClick={this.handleClick}>点我,传递给父组件数据</button>
</div>
)
}
}
export default Father
三,兄弟组件通讯
1, 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
2,父组件提供共享状态和共享方法
3,要通讯的子组件只需通过props接收状态或操作状态
class Counter extends React.Component {
state = {
count: 0
}
onIncrement = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<Child1 count={this.state.count}/>
<Child2 onIncrement = {this.onIncrement}/>
</div>
)
}
}
const Child1 = (props) => {
return <h1>计数器:{props.count}</h1>
}
const Child2 = (props) => {
return <button onClick={()=> props.onIncrement()}>+1</button>
}
4, Context (跨组件传递数据)
使用步骤
1,调用React.createContext()创建Provider(提供数据)和Consumer(消费数据),两个组件
const { Provider, Consumer } = React.createContext()
<Provider>
<div className="App">
<Child1 />
</div>
</Provider>
<Provider value="pink" >
<Consumer>
{ (data) => <span>要接受的数据{ data }</span> }
</Consumer>
import React from 'react'
// 创建两个组件
const { Provider, Consumer } = React.createContext()
class App extends React.Component {
render() {
return (
<Provider value="pink">
<div className="app">
<Node />
</div>
</Provider>
)
}
}
const Node = (props) => {
return (
<div className="node">
<SubNode />
</div>
)
}
const SubNode = (props) => {
return (
<div className="subnode">
<Child />
</div>
)
}
const Child = () => {
return (
<div className="child">
<Consumer>
{
data => <span>子组件拿到的:{data}</span>
}
</Consumer>
</div>
)
}
export default App;
functin Hello(props) {
return (
<div>
组件子节点: {props.children}
</div>
)
}
<Hello>我是子节点</Hell0>
import React from 'react'
import ReactDOM from 'react-dom'
const MyApp = props => {
// 组件标签的子节点是函数的获取方式
props.children()
return (
<div>
<h1>组件标签的子节点,下面是文本,元素,的获取方式</h1>
{props.children}
</div>
)
}
ReactDOM.render(
<MyApp>
{
() => console.log('这是一个函数节点')
}
</MyApp>,
document.getElementById('root')
)
App.propTypes = {
colors: PropTypes.array
}
npm i prop-types
import PropTypes from 'prop-types'
Hello.propTypes = {
age: PropTypes.number
}
// 常见类型
optionalFunc: PropTypes.func,
// 必须按
requiredFunc: PropTypes.func.isRequired
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontsize: PropTypes.number
})
App.propTypes = {
a: PropTypes.number,
fn: PropTypes.func.isRequired,
tag: PropTypes.element,
filter: PropTypes.shape({
area: PropTypes.string,
price: PropTypes.number
})
}
- 常见类型: array, bool, func, number, object, string
- React 元素类型: element
- 必填项: isRequired
- 特定结构的对象: shape({})
常见的props校验规则:
(4)校验规则通过 PropTypes对象来指定
(3) 使用 组件名.propTypes = {} 来给组件的props添加校验规则
(2) 导入prop-types包
(1)安装 prop-types
使用该功能的步骤:
3,props校验: 允许在创建组件的时候,就指定props的类型,格式
2,children属性与普通的props一样,值可以是任意值,文本,元素,组件,函数
1,children属性: 表示组件标签的子节点。当组件标签有子节点的时候,props就会有该属性
5, props 深入
Consumer用来消费数据的,通过回调函数的参数接收value值
Provider用来提供数据的,通过value,
总结:Context提供了两个组件,Provider和Consumer,
DEMO
4, 调用Consumer组件接收数据
3,设置value属性,表示要传递的数据
2,使用Provider组件作为父节点
第五节:组件的生命周期
1,组件的生命周期概述
为什么要理解组件的生命周期?
意义:组件的生命周期有助于理解组件的运行方式,完成更负责的组件功能,分析组件错误原因
组件的生命周期: 组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
钩子函数: 生命周期的每一个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
钩子函数的作用: 为开发人员在不同阶段操作组件提供了时机
注意: 只有类组件才有生命周期
2,生命周期的三个阶段
1, 创建时 2,更新时 3,卸载时
(1) 每个阶段的执行时机
(2)每个阶段钩子函数的执行顺序
(3) 每个阶段钩子函数的作用
-
创建时 (挂载阶段)
执行时机:组件创建时,页面加载时
执行顺序:
constructor() —> render() —> componentDidMount
| 钩子函数 | 触发时机 | 作用 |
|---|---|---|
| constructor | 创建组件时,最新秩序 | 1,初始化state 2,为事件处理程序绑定this |
| render | 每次组件渲染都会触发 | 渲染UI,(注意不能调用setState()) |
| componentDidMount | 组件挂载(完成DOM渲染)后 | 1,发送网络请求 2,DOM操作 |
为什么不能在render()里面调用setState() ?
因为我们知道,setState是用来更新state里面的状态的,一旦state里面的状态发生了改变,就会更新UI,如果在
render()里面调用this.setState(),会发生递归现象
- 更新时
执行时机:1, 接收new props, 2,setState(), 3,forceUpdate() 说明: 以上三者任意一种变化,组件就会重新渲染 执行顺序: render() —> componentDidUpdate()
| 钩子函数 | 触发时机 | 作用 |
|---|---|---|
| render | 每次组件渲染都会触发 | 渲染UI,与挂载阶段是同一个render |
| componentDidUpdate | 组件更新(完成DOM渲染)后 | 1,发送网络请求 2,DOM操作 3,注意:如果要设置setState(), 必须要放在一个if 条件中 |
为什么在componentDidUpdate里面调用setState()必须要放在if条件中?
因为render()方法执行完之后,就开始执行componentDidUpdate, 如果在setState里面更新了state状态,会
导致render()重新执行,render()执行完之后,componentDidUpdate又继续执行,所以会导致递归现象
if(prevProps.count !== this.props.count) {
this.setState({})
// 发送ajax请求的代码
}
-
卸载时
-
执行时机: 组件从页面中消失
钩子函数 触发时机 作用 componentWillUnmount 组件卸载(从页面中消失) 执行清理工作,比如:清理定时器
不常用的钩子函数
已经废弃的钩子函数:
componentWillMount
componentWillReceiveProps
componentWillUpdate
新增的钩子函数(但都不常用)
getDerivedStateFromProps
getSnapshotBeforeUpdate
shouldComponentUpdate(常用)
3, 总结
1,代码优化,推荐给 render props模式添加props校验
Mouse.propTypes = {
children: PropTypes.func.isRequired
}
2,应该在组件卸载时,解除mousemove事件绑定
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
3,DEMO
import React from 'react'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
console.log('生命周期钩子函数: constructor')
}
componentDidMount() {
console.log('生命周期钩子函数: componentDidMount')
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
// this.forceUpdate() 强制更新
}
render() {
console.log('生命周期钩子函数: render')
return (
<div>
{
this.state.count > 3 ?
<p>豆豆被打死了</p> :
<Counter count={this.state.count}/>
}
<button onClick={this.handleClick}>打豆豆</button>
</div>
)
}
}
class Counter extends React.Component {
componentDidMount() {
// 开启定时器
this.timerId = setInterval(() => {
console.log('子组件----定时器开始执行')
})
}
// 注意:如果要调用setState(), 必须放在一个if中
componentDidUpdate(prevProps) {
console.log('子组件----生命周期钩子函数: componentDidUpdate')
// 获取DOM操作
const title = document.getElementById('title')
console.log(title.innerHTML)
console.log('上一次的props:', prevProps, '当前的props:', this.props)
if(prevProps.count !== this.props.count) {
this.setState({})
// 发送ajax请求的代码
}
}
render() {
console.log('子组件----生命周期钩子函数: render')
return <h1 id="title">统计豆豆被打次数{this.props.count}</h1>
}
componentWillUnmount() {
console.log('子组件----生命周期钩子函数: componentWillUnmount')
// 清理定时器
clearInterval(this.timerId)
}
}
export default App;
第六节:React 高阶组件
1, 高阶组件
目的: 实现状态逻辑复用
采用包装模式:高阶组件就相当于手机壳,给手机提供保护功能,通过包装组件,增强组件功能。
思路分析
- 高阶组件(HOC, Highter-Order-Component)就是一个函数, 接收要包装的组件,返回增强后的组件
- 高阶组件内部创建一个类组件,这个类组件中提供服用状态的逻辑代码,通过prop将复用的状态传递给被包装组件
const EnComp = withHOC(wrapComponet)
创建高阶组件步骤
(1)创建一个函数,名称约定以with开头
(2)指定函数参数,参数应该以大写字母开头(参数是要渲染的组件)
(3)在函数内部创建一个类组件,提供复用的逻辑代码,并返回
(4)在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
(5)调用高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
(6) 高阶组件设置: displayName, 使用高阶组件存在的问题,得到的两个组件名称相同,原因是默认情况下,为高阶组件设置displayName便于区分不同的组件
import React from 'react'
import img from './logo.svg'
// 创建高阶组件
function withMouse(WrappedComponent) {
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 控制监听鼠标状态逻辑
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
return <WrappedComponent {...this.state}></WrappedComponent>
}
}
// 设置displayName
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
return Mouse;
}
// 测试高阶组件
const Position = props => (
<p>
鼠标当前位置: (x: {props.x}, y: {props.y})
</p>
)
const Cat = props => (
<img
src={img}
alt=""
style={{
position: 'absolute',
width: '35px',
height: '35px',
top: props.y - 60,
left: props.x - 60
}}
/>
)
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
// 获取增强后的组件
const MousePositon = withMouse(Position)
// 调用高阶组件来增强组件功能
const MouseCat = withMouse(Cat)
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<MousePositon></MousePositon>
<MouseCat></MouseCat>
</div>
)
}
}
(7)高阶组件 传递props丢失问题?
因为高阶组件没有往下传递props
解决方式: 渲染WrappedComponent时,将state和this.props一起传递给组件
return <WrappedComponent {...this.state} {...this.props}></WrappedComponent>
2, 总结
React组件进阶
1, 组件通讯是构建React应用必不可少的一环
2, props的灵活性让组件更加强大
3, 状态提升是react组件的常用模式
4, 组件生命周期有助于理解组件的运行过程
5, 钩子函数让开发者可以在特定的时机执行某些功能
6, render props模式和高阶组件都可以实现组件状态逻辑复用
7, 组件极简模式: (state, props) => UI