文章目录
本篇笔记主要学习以下几个方面,从学习目标中就可以了解到整篇笔记的框架脉络,欢迎大家补充!!一起学习!!
0、学习目标
- 组件通讯介绍
- 组件的props
- 组件通讯的三种方式
- Context
- props深入
- 组件的生命周期
- render-props和高阶组件
1、组件通讯和Props
(1) 组件props介绍
组件是独立且封闭的单元,默认情况下组件只能使用自己的数据。多个组件之间不可避免的需要共享数据,这个过程就是组件通讯。要接收外部数据应该通过props来实现。每个父组件都可以提供 props 给它的子组件,从而将一些信息传递给它。
props的作用:接收传递给组件的数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
<Hello name="jack" age={19}/>
...
// 函数组件
function Hello(props){
return (
<div> 接收到数据:{props.name}</div>
)
}
// 类组件
class Hello extends React.Component {
render(){
return (
<div> 接收到数据:{this.props.age}</div>
)
}}
(2) 组件props的特点:
- 传递的数据类型不限,甚至可以传递函数
<Hello name="jack"
age={19}
colors={['red','blue','pink']},
fn={()=>console.log('123')},
tag={<p>456</p>}
/>
- props是只读对象,不可修改
- 在使用类组件时,如果写了构造函数,应该将props传递给super(),否则无法在构造函数中获取到props
(3)组件通讯的三种方式
1) 父传子
步骤:
- 父组件提供要传递的state数据
- 给子组件标签添加属性,值为state的数据
- 子组件通过props接收来自父组件传递的数据
class Parent extends React.Component {
state = {
lastName: '徐'
}
render(){
return (
<div>
<Child name={this.state.lastName} />
</div>
)
}}
function Child(props){
return(
//这是函数组件,没有this,所以是props.name
<div> 接收到数据:{props.name}</div>
)
}
2)子传父
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
- 父组件提供回调函数(用于接收数据,谁接收数据谁提供回调函数)
- 将函数作为属性值,传递给子组件
- 子组件通过props调用回调函数
- 将子组件的数据作为参数传递给回调函数
// 父组件
class Parent extends React.Component {
state = {
parentMessage : ''
}
//1、提供回调函数,用来接收数据
getChildMessage = data >{
console.log(data) // 打印出来子组件传递的数据 123
// 如果要在父组件中展示传递过来的数据
this.setState({
parentMsg: data
})
}
render(){
return (
<div>
父组件:{this.state.parentMsg}
// 2、将函数作为属性值传给子组件
<Child getMsg={this.getChildMessage}/>
</div>
)
}
}
// 子组件
class Child extends React.Component {
state={
msg: '123'
}
//3、子组件调用父组件中传递过来的回调函数
handleClick = ()=>{
//4、参数:要传递的数据【关键】
this.props.getMsg(this.state.msg)
}
render(){
return (
<div>
<button onClick={this.handleClick}> </button>
</div>
)
}
}
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>
}
cosnt Child2 = (props) => {
return <button onClick={props.onIncrement }>+1</button>
}
ReactDOM.render(<Counter />, document.getElementById('root'))
2、Context的基本内容
Context:跨组件传递数据
如果组件之间嵌套多层,可以用Context实现组件通讯,Context提供了两组件:Provider和Consumer
使用步骤:
- 调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件
const {Provider, Consumer} = React.createContext()
- 使用Provider组件作为父级节点
<Provider>
<div id="App">
<Child1 />
</div>
</Provider>
- 设置Value属性,表示要传递的数据。该值会传递到回调函数的参数
<Provider value="pink">
<div id="App">
<Child1 />
</div>
</Provider>
- 调用Consumer组件接收数据
<Consumer>
//会显示 pink 即provider的组件的属性值
{ data => <span>我是子节点:{data}</span>}
<Consumer/>
3、props深入
(1) children属性
表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
子节点可以是组件、JXS、函数等,与普通的props一样
const App = props => {
//子节点为函数时
props.children()
console.log(props)
return(
<div>
// <p>组件标签的子节点:{props.children}</p>
</div>
)
}
ReactDOM.render(
<App>
{
()=>console.log('子节点为函数')
}
</App>
)
(2)props校验
对于组件来说,无法保证组件使用者传入什么格式的数据
props校验:允许在创建组件的时候就指定Props的格式、类型,捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
使用步骤:
- 安装
npm i props-types
- 导入
import PropTypes from 'prop-types';
- 使用
使用组件名.PropTypes= {}来给组件props添加校验规则。下面是几种约束规则。
App.ProtoTypes ={
//约定colors属性数组类型
App.PropTypes={
color: PropTypes.array,
a: PropTypes.number,
fn:PropTypes.func.isRequired,
tag:PropTypes.element,
filter: PropTypes.shape({
area: PropTypes.string,
price: PropTypes.number
})
}
4、组件的生命周期
(1)生命周期的概述
- 意义:组件的生命周期有助于理解组件的运行方式、完成更加复杂的组件功能、分析组件的错误原因等
- 组件的生命周期:组件被创建到挂在到页面中运行,再到组件不用时卸载的过程。当组件被添加到屏幕上时,它会进行组件的挂载。当组件接收到新的 props 或 state 时,通常是作为对交互的响应,它会进行组件的更新,当组件从屏幕上移除时,它会进行组件的卸载。
- 生命周期的每个阶段伴随着一些方法的调用,这就是生命周期的钩子函数,为我们在不同开发阶段提供了时机。
- 只有类组件才有生命周期
(2)生命周期的三个阶段
首先放一张生命周期的总体阶段概述图
1)创建时(挂载阶段)
constructor()-> render() ->componentDidMount
class App extends React.Component{
constructor(props){
super(props)
console.warn('1、生命周期钩子函数:constructor')
this.state = {
count : 0
}
}
//发送请求、操作DOM
componentDidMount(){
console.warn('3、生命周期钩子函数:constructor')
}
//每次渲染时都会触发
render(){
console.warn('2、生命周期钩子函数:componentDidMount')
return(
<div>
<p></p>
</div>
)
}
}
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时最先执行 | 1、初始化state 2、为事件处理程序绑定this |
render | 每次组件渲染都会触发 | 渲染U(不能在这调用setState)I |
componentDidMount | 组件挂载(完成DOM渲染)后 | 1、发送网络请求 2、DOM操作 |
2)更新时
对应上面的总图可以看出:
执行时机—setState()、forceUpdate()、组件接收到新的props
以上三种任一变化,就会重新渲染
3)卸载时
执行时机:组件从页面中消失
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作,比如清理定时器等 |
举个定时器的例子:
class Counter extends React.Component {
componentDidMount() {
this.timerId = setInterval(()=>{
console.log('定时器在执行')
},500)
}
render(){
return(
//省略
)
}
componentWillUnmount() {
console.warn('生命周期钩子函数:componentWillUnmount')
clearInterval(this.timerId)
}
}
5、render-props
(1)组件复用
复用相似的功能:state和操作state的方法(组件状态逻辑)
两种方式:render props模式 和 高阶组件
(2)render props思考
思路:将要复用的state和操作state的方法封装到一个组件中
- 如何拿到该组件复用的state?:在使用组件时,添加一个值为函数的Prop,通过函数参数来获取(需要组件内部实现)
<Mouse render={(mouse)=>{}} />
- 如何渲染任意的UI?:使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)
<Mouse render={(mouse)=>{
//
<p>鼠标当前的位置 {mouse.x},{mouse.y}<p>
}} />
(3)render props模式
使用步骤:
1、创建Mouse组件,在组件中提供复用的状态逻辑代码(状态和逻辑)
2、将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
3、使用props.render()的返回值作为要渲染的内容
// 创建Mouse组件:提供复用的状态逻辑代码
class Mouse extends React.Component {
state ={
x: 0,
y: 0
}
// 鼠标移动的事件处理程序
hanldeMouseMove = (e)=>{
this.setState({
x: this.e.clientX,
y: this.e.clientY
})
}
render(){
return(
// 暴露到组件外部
this.props.render(this.state)
)
}
// 监听鼠标移动事件
componentDidMount(){
window.addEventListener('mousemove', this.hanldeMouseMove)
}
}
class App extends React.Component {
render(){
return(
<div>
<h1>render props模式</h1>
<Mouse render={(mouse)=> <p>鼠标当前的位置 {mouse.x},{mouse.y}</p>}/>
</div>
)
}
}
把prop是一个函数并且告诉组件要渲染上面内容的技术叫做:render props模式
【推荐】:使用children 代替render属性
<Mouse>
{
{(mouse)=> <p>鼠标当前的位置 {mouse.x},{mouse.y}</p>}
}
</Mouse>
// 组件内容
this.props.children(this.state)
//给render props模式添加props校验 对children属性(优化)
Mouse,PropTypes = {
children: PropTypes.func.isRequired
}
6、高阶组件
高阶组件:(HOC),其目的是实现状态逻辑复用, 接收要包装的组件,返回增强后的组件。
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent
(1)使用步骤
- 创建一个函数,约定以with开头
- 指定函数参数,参数以大写字母开头(作为渲染的组件)
- 在函数的内部创建一个类组件,在类组件中提供复用的状态逻辑代码,并返回
- 在该组件中渲染参数组件,同时将状态通过Props传递给参数组件
- 调用该高阶函数,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
看下面代码应该有点感觉
//创建高阶组件
//1、创建一个以with开头的函数
function withMouse(WrappedComponent){
//2、在函数内部创建类组件,里面写复用的代码逻辑
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)
}
compoentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
render(){
// 状态通过Props传递给参数组件
return <WrappedComponent {...this.state}/>
}
}
}
const Position = props => {
<p>
鼠标当前的位置 :(x: {props.x},y:{props.y})
</p>
}
// 这个{img} 需要导入
const Cat = prop => {
<img src={img} style={{
position: "absolute",
top: props.y -64,
left: props.x -64
}}/>
}
//调用高阶组件 实参:Position 实际上替代WrappedComponent 获取增强后的组件:
const MousePosition = withMouse(Position)
const CatPosition = withMouse(Cat)
class App extends React.Component{
render(){
<div>
<h1>高阶组件</h1>
{/* 渲染增强后的组件*/}
<MousePosition />
<CatPosition />
</div>
}
}
ReactDOM.render(<App/>, document.getElementById('root'))
(2)displayName
上述代码中存在问题:得到的两个组件名称相同
原因:默认情况下,React使用组件名称作为displayName
解决方式:为高阶组件设置displayName 便于调试时区分
//设置displayName
Mouse.displayName=`withMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
(3)传递props
上述代码中存在问题:Props丢失
原因:高阶组件没有往下传递props
解决方式:渲染WrappedComponent组件时,将state 和this.props一起传递给组件
//原来
return <WrappedComponent {...this.state}/>
//修改后将state 和this.props一起传递给组件
return <WrappedComponent {...this.state} {...this.props}/>
7、总结
- 组件通讯是构建React应用必要一环(父传子、子传父、兄弟组件)
- 状态提升是React组件常用的模式
- 生命周期有助于理解组件的运行过程
- 钩子函数可以在特定的时期执行功能
- render props模式和高阶组件都可以实现组件状态逻辑复用