五、React组件进阶
学习目标
- 能够使用props接受上数据
- 能够实现父子组件之间的通讯
- 能够实现兄弟组件之间的通讯
- 能够给组件添加props校验
- 能够说出生命周期常用的钩子函数
- 能够知道高阶组件的作用
1.组件通讯介绍
组件是独立封闭的单元,默认情况下,只能使用自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯
2.组件的props
-
组件是封闭的,要接受外部数据应该通过props来实现
-
props的作用:接收传递给组件的数据
-
传递数据:给组件标签加属性
-
接收数据:函数组件通过参数props接收数据,类组件通过 this.props接收数据
import React from 'react' import ReactDOM from 'react-dom' /** props 通过函数创建组件 */ // 2.接收数据 通过函数创建组件 const Hello = props =>{ return ( <div> <h1>props:{props.name}</h1> //props.name --> {name:"jack"} Object </div> ) } /** props 通过Class 创建组件 */ class Hello extends React.Component { render(){ // 使用 console.log(this.props) return( <div> <h1> props: {this.props.age} </h1> </div> ) } } // 1. 传递数据 渲染 ReactDOM.render(<Hello name="jack" age={19} / >,document.getElementById('root'))
特点
-
可以给组件传递任意类型的数据
-
props是只读的对象,只能读取属性的值们无法修改对象
-
注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在工业早函数中获取到props
// 示例 class Hello extends React.Component { constructor(props){ // 推荐将props 传递给父亲构造函数 super(props) } render(){ // 使用 console.log(this.props) return( <div> <h1> props: {this.props.age} </h1> </div> ) } } // 1. 传递数据 渲染 ReactDOM.render(<Hello name="jack" age={19} / >,document.getElementById('root'))
-
3.组件通讯的三种方式
3.1 父组件传递数据给子组件
-
父组件提供要传递的state数据
-
给子组件标签添加属性,值为state中的数据
-
组组件中通过props接收父组件中传递的数据
calss Parent extends React.Component{ state ={ lastName:"老王" } Render(){ return ( <div className="ParentCom"> 我是父组件 <Child name="this.state.lastName" /> </div> ) } } const Child =(props)=>{ return (<div className="childCom"> 我是子组件---》我接收到的值:{props.name} </div>) } ReactDOM.render(<Parent / >,document.getElementById('root'))
3.2 子组件传递数据给父组件
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
-
父组件提供一个回调函数用于接收数据(谁要接收数据,谁就要提供函数)
-
将该函数作为属性的值,传递给子组件
class Parent extends React.Component{ state={ parentmsg:"" } // 先提供一个回调函数,来接收数据 getChildMsg = data=>{ console.log('接收到子组件传给父组件的值', data) this.setState(){ this.state.parentmsg = data } } render(){ return ( <div> 我是父组件 :{this.state.parentmsg} <Child getMsg={this.getChildMsg}/> </div> ) } } class Child extends React.Component{ sate={ msg:'刷抖音' } handleClick = ()=>{ //子组件调用 this.props.getMsg(this.state.msg) } render(){ return ( <div> 我是子组件 <button onClick={ this.handleClick }>点我给父组件传递数据</button> </div> ) } } ReactDOM.render(<Parent / >,document.getElementById('root'))
3.3 兄弟组件
- 将共享状态提升到最近的公共父组件中,有公共父组件管理这个状态
- 思想:状态提升
- 公共父组件职责: 1.提供共享状态2.提供操作共享状态的方法
- 要通讯的子组件只通过props接收状态或操作状态的方法
class Counter extends React.Component{
// 提升状态
state:{
count:0
}
// 提供回调函数
onIncreament =()=>{
this.setState({
count :this.state.count +1
})
}
render(){
return (
<div>
<Child1 count={this.state.count}/>
<Child2 onIncreament= {this.onIncreament}/>
</div>
)
}
}
const Child1 =props=>{
return <h1>计算器:{props.count}</h1>
}
const Child2 =props=>{
return
<button onClick={()=>props.onIncreament()}>+1</h1>
}
ReactDOM.render(<Counter / >,document.getElementById('root'))
4. Context
思考:App组件要传递数据给Child组件,该如何处理?
- 更好的姿势:使用Context
- 作用:跨组件传递数据(比如:主题、语言)
使用步骤
-
调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件
const {Provider,Consumer} = React.createContext
-
使用Provider组件作为父节点
<Provider> <div className="app"> <Child1/> </div> </Provider>
-
设置value属性,表示要传递的数据
<Provider value="pink"> </Provider>
-
调用Consumer组件接收数据
<Consumer> {data => <span>data参数表示接收到的数据----{data}</span>} </Consumer>
总结:
- 如果两个组件是远方亲戚(比如:,嵌套多层)可以使用Context实现组件通讯
- Context提供了两个组件:Provider和Consumer
- Provider组件:用来提供数据
- Consumer组件:用来消费数据
5. props深入
5.1 children属性
- children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
- children属性与普通props一样,值可以是任意值(文本、React元素、组件,甚至是函数
const App =() =>{
return
(<div>
<h1> 组件标签的子节点: </h1>
{props.children}
</div>)
}
ReactDOM.render(<App>
你好 我是子节点
</App>,document.getElementById('root'))
5.2 props校验
-
对于组件来说,props是外来的,无法保证组件使用者传入的什么格式的数据
-
如果传入的数据格式不对,可能会导致组件内部报错
-
关键问题:组建的使用者不知道明确的错误原因
-
props校验:允许在创建组件的时候,就指定props的类型、格式等
-
作用:捕获使用组件时因为props导致的错误,给明确的错误提示,增强组件的健壮性
App.propTypes = { colors:PropTypes.array }
使用步骤
-
安装包 prop-types(yarn add prop-types/npm i props-types )
yarn add prop-types/npm i props-types
-
导入 prop-types 包
-
使用组件名.propTypes = {}来给组件的props添加校验规则
-
校验规则通过PropTypes对象来指定
// 先导入 prop-types 包 import PropTypes from 'prop-types' const App =props =>{ const arr = props.colors const lis = arr.map((item,index)=> <li key={index}>{item}</li>) return (<ul> {lis} </ul>) } //添加props 校验 App.propTypes = { colors:PropTypes.array } ReactDOM.render(<App colors={['red','black']} />,document.getElementById('root'))
约束规则
- 常见类型:array,bool,func,number、object、string
- React元素类型:element
- 必填项:isRequired
- 特定结构的对象:shape({})
import PropTypes from 'prop-types'
const App = props =>{
return
(<div>
<h1> props校验: </h1>
</div>)
}
//添加props 校验
//属性 a 的类型: 数值(number)
//属性 fn 的类型: 函数(func) 并且为必填项
//属性 tag 的类型: React 元素(element)
//属性 filter 的类型: 对象 ({area:'上海',price:1999})
App.propTypes = {
a:PropTypes.number,
fn:PropTypes.func.isRequired,
tag:PropTypes.element,
filter:PropTypes.shape({
area:PropTypes.string,
price:PropTypes.number
}),
}
ReactDOM.render(<App colors={['red','black']} />,document.getElementById('root'))
5.3 props的默认值
-
场景:分页组件 ----> 每页显示条数
-
作用:给props设置默认值,在未传入props时生效
// 先导入 prop-types 包 import PropTypes from 'prop-types' const App =props =>{ return (<div> 此处展示props的默认值:{props.pageSize} </div> } //添加props 校验 App.defaultProps = { pageSize:10 } ReactDOM.render(<App colors={['red','black']} />,document.getElementById('root'))
6.组件的生命周期
6.1组件的生命周期概述
- 意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
- 组件的生命周期:组件从未被创建到挂载到页面中运行,再到组件不用时卸载的过程
- 生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
- 钩子函数的作用:为开发人员在不同阶段操作组件提供了时机
- 只有类组件才有生命周期
6.2生命周期的三个阶段
- 每个阶段的执行时机
- 每个阶段钩子函数的执行顺序
- 每个阶段钩子函数的作用
1.创建时(挂载阶段) constructor()
- 执行时机:组件创建时(页面加载时)
- 执行顺序:
- constructor() ---------> render() ----------->componentDidMount[外链
class App extends React.Component{
constructor(props){
super(props)
console.warn('生命周期钩子函数, constructor')
}
// 1. 能够进行DOM操作 2. 发送ajax请求,获取远程数据
componentDidMount(){
console.warn('生命周期钩子函数, componentDidMount')
const title = document.getElementById('title')
console.log('title',title) //在此出是可以拿到的 在constructor和render都不可以拿到
}
render(){
// 错误演示!!! 不能在render中调用setState
// this.setState({
// count:1
// })
console.warn('生命周期钩子函数, render')
return (
<div>
<h1 id="title">统计豆豆被大的次数</h1>
<button id="btn">打豆豆</button>
</div>
)
}
}
ReactDOM.render(<App />,document.getElementById('root'))
2.更新时(更新阶段) render()
- 执行时机:1.setState() 2.forceUpdate() 3.组件接收到新的props
- 说明:以上三者任意一种变化,组件就会重新渲染
// componentDidUpdate() 如果要使用setState必须使用 if
componentDidUpdate(prevProps){
console.log('上一次的props',prevProps,',当前的props',this.props)
if(prevProps.count !==this.props.count){
this.setState({})
// ajax 请求
}
}
3.卸载时(卸载阶段)
- 执行时机:组件从页面中消失
componentWillUnmount(){ //组件卸载时调用
console.log('被停止消失了')
}
6.3不常用钩子函数介绍
新版完整生命周期钩子函数(知道):
7.render-props 和高阶组件
7.1 React组件复用概述
- 思考:如果两个组件中的部分功能相似相同,该如何让处理?
- 处理方式:复用相似的功能(联想函数封装)
- 复用什么?1.state 2. 操作state的方法 (组件状态逻辑)
- 两种方式:1. render-props 模式 2. 高阶组件(HOC)
- 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)
使用步骤
- 创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态 2.操作状态的方法)
- 将要复用的状态作为 props.render(state)方法的参数,暴露到组件外部
- 使用props.render() 的返回值作为要渲染的内容
示例
import React from 'react'
import ReactDOM from 'react-dom'
// 创建Mouse组件
class Mouse extends React.Component{
state={
x:0,
y:0
}
// 鼠标移动时间的处理程序
handleMosuseMove = e =>{
this.setState({
x:e.clientX
y:e.clientY
})
}
// 监听鼠标移动事件
componentDidMount(){ window.addEventListener('mousemove',this.handleMosuseMove)
}
render(){
return this.props.render(this.state)
}
}
class App extends React.Component{
render(){
return(
<div>
<h1> render props 模式 </h1>
<Mouse render={ mouse => return (<p>当前鼠标 x:{mouse.x} y:{mouse.y} </p>) } />
</div>
)
}
}
// 渲染
ReactDOM.render(<App>,document.getElementById('root'))