【React学习笔记】从0开始——03React组件进阶

这是前面两篇:
【React学习笔记】从0开始——01基本使用
【React学习笔记】从0开始——02组件基础

本篇笔记主要学习以下几个方面,从学习目标中就可以了解到整篇笔记的框架脉络,欢迎大家补充!!一起学习!!

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) 父传子

步骤:

  1. 父组件提供要传递的state数据
  2. 给子组件标签添加属性,值为state的数据
  3. 子组件通过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)子传父

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数

  1. 父组件提供回调函数(用于接收数据,谁接收数据谁提供回调函数)
  2. 将函数作为属性值,传递给子组件
  3. 子组件通过props调用回调函数
  4. 将子组件的数据作为参数传递给回调函数
// 父组件
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
使用步骤:

  1. 调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件
const {Provider, Consumer} = React.createContext()
  1. 使用Provider组件作为父级节点
<Provider>
	<div id="App">
		<Child1 />
	</div>
</Provider>
  1. 设置Value属性,表示要传递的数据。该值会传递到回调函数的参数
<Provider value="pink">
	<div id="App">
		<Child1 />
	</div>
</Provider>
  1. 调用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导致的错误,给出明确的错误提示,增加组件的健壮性
使用步骤:

  1. 安装
npm i props-types
  1. 导入
import  PropTypes from 'prop-types';
  1. 使用
    使用组件名.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)使用步骤

  1. 创建一个函数,约定以with开头
  2. 指定函数参数,参数以大写字母开头(作为渲染的组件)
  3. 函数的内部创建一个类组件,在类组件中提供复用的状态逻辑代码,并返回
  4. 在该组件中渲染参数组件,同时将状态通过Props传递给参数组件
  5. 调用该高阶函数,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中

看下面代码应该有点感觉

//创建高阶组件
//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、总结

  1. 组件通讯是构建React应用必要一环(父传子、子传父、兄弟组件)
  2. 状态提升是React组件常用的模式
  3. 生命周期有助于理解组件的运行过程
  4. 钩子函数可以在特定的时期执行功能
  5. render props模式高阶组件都可以实现组件状态逻辑复用
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值