目录
1. 组件的props
- 组件是封闭的,要接收外部数据应该通过props来实现
props的作用:接收传递给组件的数据
- 传递数据:
给组件标签添加属性
- 接收数据:函数组件通过
参数props
接收数据,类组件通过this.props
接收数据
函数组件接收外部数据:
import React from 'react'
import ReactDOM from 'react-dom'
// 2、接收数据
const App = props => {
// props是一个对象
return (
<div>
<h1>props:{
props.name}</h1>
</div>
)
}
// 渲染组件 1、传递数据:传递了一个名字和一个数字。注意:“19”:为字符串,{19}:为number 19
ReactDOM.render(<App name="guili" age={
19} />, document.getElementById('root'))
类组件接收外部数据:
import React from 'react'
import ReactDOM from 'react-dom'
// 2、接收数据
class App extends React.Component {
render(){
// props是一个对象
return (
<div>
<h1>props:{
this.props.age}</h1>
</div>
)
}
}
// 渲染组件 1、传递数据:传递了一个名字和一个数字。注意:“19”:为字符串,{19}:为number 19
ReactDOM.render(<App name="guili" age={
19} />, document.getElementById('root'))
特点
- 可以给组件传递任意类型的数据
props
是只读
的对象
,只能读取属性的值,无法修改对象- 注意:使用
类组件
时,如果写了构造函数,应该将props传递给super()
,否则,无法在构造函数中获取到props!
代码实现:
函数组件接收数据:
import React from 'react'
import ReactDOM from 'react-dom'
// 2、接收数据
const App = props=> {
props.fn()//接收一个函数
return (// props是一个对象
<div>
<h1>props:{
this.props.age}</h1>
{
this.props.tag}//接收标签
</div>
)
}
// 渲染组件 1、传递数据:传递了一个名字和一个数字。注意:“19”:为字符串,{19}:为number 19
ReactDOM.render(<App
name="guili" //可以传递字符串
age={
19} //可以传递number
colors={
['red','green','blue']}//可以传递数组
fn={
()=>console.log('这是一个函数')}//可以传递函数
tag={
<p>这是一个p标签</p>}//可以传递标签
/>, document.getElementById('root'))
类组件接收数据:
import React from 'react'
import ReactDOM from 'react-dom'
// 2、接收数据
class App extends React.Component {
//推荐使用props作为constructor的参数!!
constructor(props) {
//推荐将props传递给父类构造函数
super(props)
}
render() {
return <div>接收到的数据:{
this.props.age}</div>
}
}
// 渲染组件 1、传递数据:传递了一个名字和一个数字。注意:“19”:为字符串,{19}:为number 19
ReactDOM.render(<App
name="guili" //可以传递字符串
age={
19} //可以传递number
colors={
['red','green','blue']}//可以传递数组
fn={
()=>console.log('这是一个函数')}//可以传递函数
tag={
<p>这是一个p标签</p>}//可以传递标签
/>, document.getElementById('root'))
2. 组件通迅的三种方式
- 父组件–>子组件
- 子组件–>父组件
- 兄弟组件
父组件传递数据给子组件
- 父组件提供要传递的state数据
- 给子组件标签添加属性,值为
state
中的数据- 子组件通过
props
接收父组件中传递的数据
// 父组件
class Parent extends React.Component {
state = {
lastName:'曾'}
super(props)
render() {
return (
<div>
传递数据给子组件<Child name={
this.state.lastName}></Child>
</div>
)
}
}
// 子组件
const Child = () => {
return (
<div>
<p>子组件,接收到父组件的数据:{
props.name}</p>
</div>
)
}
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
// 父组件
class Parent extends React.Component {
state = {
lastName:'曾'
}
super(props)
render() {
return (
<div className='parent'>
父组件:
<Child name={
this.state.lastName}></Child>
</div>
)
}
}
// 子组件
const Child = () => {
return (
<div className='child'>
<p>子组件,接收到父组件的数据:{
props.name}</p>
</div>
)
}
ReactDOM.render(<Parent />, document.getElementById('root'))
子组件传递数据组父组件
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
- 父组件提供一个回调函数(用于接收数据)
- 将该函数作为属性的值,传递给子组件
- 子组件通过props调用回调函数
- 将子组件的数据作为参数传递给回调函数
注意:回调函数中this的指向问题
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
// 父组件
class Parent extends React.Component {
// 提供回调函数,用来接收数据
getChildMsg = data => {
console.log('接收到子组件中传递过来的数据',data)
}
render() {
return (
<div className='parent'>
<Child />
传递数据给子组件<Child name={
this.getChildMsg}></Child>
</div>
)
}
}
// 子组件
class Child extends React.Component{
state = {
msg:'桂莉'
}
handleClick = () => {
// 子组件调用父组件中传递过来的回调函数
this.props.getMsg(this.state.msg)
}
render() {
return (
<div className='child'>
子组件:{
' '}
<button onClick={
this.handleClick}>点我,给父组件传递数据</button>
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('root'))
兄弟组件
- 将
共享状态
提升到最近的公共父组件
中,由公共父组件管理这个状态- 思想:
状态提升
- 公共父组件的职责:1. 提供操作共享状态方法
- 要通迅的子组件只需通过
props
接收状态或操作状态的方法
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
// 父组件
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>
}
ReactDOM.render(<Counter/>,document.getElementById('root'))
3. Context
作用:跨组件传递数据
Context使用步骤
- 调用React.createContext()创建Provider(提供数据)和 Consumer(消费数据)两个组件
const {
Provider, Consumer } = React.createContext()
- 使用Provider组件作为父节点
<Provider>
<div className='app'>
<Node />
</div>
</Provider>
- 设置valu有属性,表示要传递的数据
<Provider value="pink">
- 调用Consumer组件接收数据
<Consumer>
{
data => <span>我是子节点:{
data}</span>}
</Consumer>
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
// 创建context得到两个组件
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 = props => {
return (
<div className='child'>
<Consumer>{
data => <span>我是子节点:{
data}</span>}</Consumer>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
总结:
- 如果两个组件是远方亲戚(比如多层嵌套)可以使用Context实现组件通迅
- Context提供了两个组件:Provider和Consumer
- Provider组件:用来提供数据
- Consumer组件:用来消费数据
4. props深入
- children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
- children属性与普通的props 一样,值可以是任意值(文本、React元素、组件、甚至是函数)
children为文本节点:
import React from 'react'
import ReactDOM from 'react-dom'
const App = props => {
return (
<div>
<h1>组件标签的子节点</h1>
{
props.Children}
</div>
)
}
ReactDOM.render(<App>我是子节点</App>, document.getElementById('root'))
children为JSX或组件节点:
import React from 'react'
import ReactDOM from 'react-dom'
const Test =()=><button>我是button组件</button>
const App = props => {
return (
<div>
<h1>组件标签的子节点</h1>
{
props.Children}
</div>
)
}
ReactDOM.render(<App>
{
/* <p>我是子节点,是一个p标签</p> */}
<Test/>
</App>, document.getElementById('root'))
children为函数节点:
import React from 'react'
import ReactDOM from 'react-dom'
const App = props => {
console.log(props)
props.children()
return (
<div>
<h1>组件标签的子节点</h1>
{
/* {props.Children} */}
</div>
)
}
ReactDOM.render(<App>
{
()=>console.log('这是一个函数子节点')}
</App>, document.getElementById('root'))
props校验
使用步骤:
- 安装prop-types,在VScode终端输入命令:
yarn add prop-types
或
npm i props types
- 导入prop-types包
- 使用
组件名.propTypes={}
来给组件的props添加校验规则
- 校验规则通过PropTypes对象来指定
// 导入prop-types包
import PropTypes form 'prop-types'
const App = props => {
return <h1>{
props.colors}</h1>
}
// 添加 props校验
App.prototypes = {
// 约定colors属性为array类型
// 如果类型不对,则报出明确错误,便于分析错误原因
colors:PropTypes.array
}
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
// 导入prop-types包
import PropTypes form '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.prototypes = {
colors:PropTypes.array
}
ReactDOM.render(<App colors={
['red','blue']}/>,
document.getElementById('root')
)
约束规则:
- 常见类型:array, bool, number, object, string。可以在这查看其他PropTypes
- React元素类型:element
- 必填项:inPequired
- 特定结构的对象:shape({})
a:PropTypes.number,//常见类型
fn:PropTypes.func.isRequired,//必选
tag:PropTypes.element,
filter:PropTypes.shape({
//特定结构的对象
area:PropTypes.string,
price:PropTypes.number
})
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
// 导入prop-types包
import PropTypes form 'prop-types'
const App = props => {
return (
<div>
<h1>props校验:</h1>
</div>
)
}
// 添加 props校验
// 属性a的类型 数值(number)
// 属性fn的类型 函数(func)并且为必填项
// 属性tag的类型 React元素(element)
// 属性filter的类型 对象({area:'上海',price:1999})
App.prototypes = {
// 如果类型不对,则报出明确错误,便于分析错误原因
a:PropTypes.number,
fn:PropTypes.func.isRequired,
tag:PropTypes.element,
filter:PropTypes.shape({
area:PropTypes.string,
price:PropTypes.number
})
}
ReactDOM.render(<App fn=()=>{
} />,document.getElementById('root')
)
props的默认值
- 场景:分布组件–>每页显示条数
- 作用:给props设置默认值,在未传入props时生效
import React from 'react'
import ReactDOM from 'react-dom'
const App =props=>{
console.log(props)
return(
<div>
<h1>在此处展示props的默认值:{
props.pageSize}</h1>
</div>
)
}
// 设置props默认值
App.defaultProps={
pageSize:20
}
// 这里传入的权限高于设置的默认值
ReactDOM.render(<App pageSize={
20} />,document.getElementById('root')
)
5. 组件的生命周期
组件生命周期概述
组件的生命周期
:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程,这个过程就叫做组件的生命周期。react在组件的生命周期中提供了一系统的钩子函数
,可以让开发者在函数中注入代码,这些代码会在适当的时候运行。为开发人员在不同阶段操作组件提供了时机。- 注意:
只有类组件才有生命周期
。函数组件每次都是重新运行函数,旧的组件即刻被销毁
生命周期的三个阶段
组件的生命周期可以被分为3个阶段:挂载阶段、更新阶段、卸载阶段
- 每个阶段的执行时机
- 每个阶段钩子函数的执行顺序
- 每个阶段钩子函数的作用
挂载阶段:
- 执行时机:组件创建时(页面加载时)
- 钩子函数执行顺序:
constructor() --> render() --> componentDidMount()
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor() | 创建组件时,最先执行 | 1. 初始化state 。2. 为事件处理程序绑定this |
render() | 每次组件渲染都会触发 | 渲染UI(注意:不能调用setState() ) |
componentDidMount() | 组件挂载(完成DOM渲染)后 | 1. 发送网络请求。2. DOM操作 |
1. constructor(): 是ES6中class的构造方法,组件被创建时会首先调用组件的构造方法 ,作用是初始化state,以及为事件处理程序绑定this
2. render():
- render()方法中,根据组件的props和state返回一个React元素,用于描述组件的UI,通常React元素使用JSX语法定义,实际上是调用React.creatElement()(JSX仅仅是createElement方法的语法糖,JSX语法被@babel/preset-react插件编译为creatElement方法,然后生成一个react元素)
- render()方法的作用是渲染UI
- 注意:在render()里面不能调用setState() componentDidMount()
3. componentDidMount():
- componentDidMount()是在组件被挂载在DOM之后调用,而且只会被调用一次。这个时候已经获取到了DOM结构,因此依赖DOM节点的操作就可以放在这个方法里面去实现。(在这一阶段,虚拟DOM已经挂载到了页面上成为了真实DOM)
- 可以调用setState()
- componentDidMount()方法的作用是:向服务器端发送网络请求,以及处理依赖DOM节点的操作
更新阶段:
- 执行时机: 1. setState() 2. forceUpdate() 3. 组件接收到新的props
- 说明:以上三者任意一种变化,组件就会重新渲染
- 执行顺序:
render() --> componentDidUpdate()
钩子函数 | 触发时机 | 作用 |
---|---|---|
render() | 每次组件渲染都会触发 | 渲染UI(与挂载阶段是同一个render()) |
componentDidUpdate() | 组件更新(完成DOM渲染)后 | 1.发送网络请求。 2. DOM操作。注意:如果要setState()必须放在一个if条件中 |
if(prevProps.count !== this.props.count){
this.setState({
})
}
组件被挂载到DOM之后,props和state都可以引起组件的更新。props引起的组件更新,本质上是由渲染该组件的父组件引起的,无论props是否改变,当父组件的render()方法每一次被调用时,都会导致组件的更新。State引起的组件更新,则是通过调用this.setState()修改组件的state来触发的。因此,父组件调用render方法或者调用this.setState都会引起组件的更新
卸载时(卸载阶段):
执行时机:组件从页面中消失
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作(比如:清理定时器等) |
6. render-props和高阶组件
通常组件复用有两种方式:
- 使用render props模式
- 高阶组件(HOC)
注意:这两种方式不是新的APOI,而是利用React自身的特点的编码技巧,演化而成的固定模式(写法)
复用什么?
- state
- 操作state的方法(组件状态逻辑)
render props模式:
- 创建要复用的组件,在组件中提供状态和操作状态的方法
- 将要复用的状态作为props.render(state)的参数暴露出去
- 注意:提供的render函数必须有返回值,另外如果将jsx写为组件的子节点,则父组件可以用this.props.children的形式
问题:
- 如何拿到该组件中复用的state? 在使用组件时,添加一个值为
函数的prop
,通过函数参数
来获取(需要组件内部实现)- 如何渲染任意的UI? 使用
该函数的返回值
作为要渲染的UI内容(需要组件内部实现)
<Mouse render={
(mouse)=>{
} }
<Mouse render={
(mouse)=>(<p>鼠标当前位置{
mouse.x},{
mouse.y}</p>)}/>
render props模式使用步骤:
- 创建Mouse组件,在组件中提供复用的状态逻辑代码(1. 状态 2. 操作状态的方法)
- 将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
- 使用props.render()返回值作为要渲染的内容
class Mouse extends React.Component{
//省略state和操作state的方法
render(){
// return null
return this.props.render(this.state)
}
}
<Mouse render={
(mouse)=><p>鼠标当前位置{
mouse.x},{
mouse.y}</p>}/>
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
// 导入图片资源
import img form './文件路径'
// 创建Mouse组件
class Mouse extends React.Component{
// 鼠标位置state
state = {
x:0,
y:0
}
// 鼠标移动事件的事件处理程序
handleMouseMove = e=>{
this.setState({
x:e.clientx,
y:e.clienty
})
}
// 监听鼠标移动事件
componentDidMount(){
window.addEventListener('mousemove',this.handleMouseMove)
}
render(){
// return null
return this.props.chlidren(this.state)
}
}
// 添加props校验
Mouse.prototypes={
chlidren:PropTypes.func.isRequired
}
class App extends React.Component{
render(){
return(
<div>
<h1>render props模式</h1>
<Mouse
render={
mouse =>{
return(
<p>
鼠标位置:{
mouse.x} {
mouse.y}
</p>
)
}}
/>
// {猫捉老鼠}
<Mouse render={
mouse =>{
return <img src={
img} alt="猫"/>
}}></Mouse>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
演示Mouse组件的复用:
- Mouse组件负责:
封装复用的状逻辑代码
(1. 状态 2. 操作状态的方法)- 状态:鼠标坐标(x, y)
- 操作状态的方法:鼠标移动事件
- 传入的render prop负责:
使用复用的状态来渲染UI结构
children代替render属性:
- 注意:并不是该模式叫render props就必须使用名为render的prop,实际上可以使用任意名称的prop
- 把prop是一个函数并且告诉组件要渲染什么内容的技术叫做:render props模式
- 推荐:使用childe
<Mouse>
{
({
x,y})=> <p>鼠标的位置是:{
x},{
y}</p>}
</Mouse>
// 组件内部
this.props.children(this.state)
代码优化:
- 推荐:给render props模式添加props校验
- 应该在组件卸载时解除mousemove事件绑定
// 添加props校验
Mouse.prototypes={
chlidren:PropTypes.func.isRequired
}
//推荐:在组件卸载时移除事件绑定
componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
// 导入图片资源
import img form './文件路径'
// 创建Mouse组件
class Mouse extends React.Component{
// 鼠标位置state
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 null
return this.props.chlidren(this.state)
}
}
// 添加props校验
Mouse.prototypes={
chlidren:PropTypes.func.isRequired
}
class App extends React.Component{
render(){
return(
<div>
<h1>render props模式</h1>
<Mouse
render={
mouse =>{
return(
<p>
鼠标位置:{
mouse.x} {
mouse.y}
</p>
)
}}
/>
// {猫捉老鼠}
<Mouse render={
mouse =>{
return <img src={
img} alt="猫"/>
}}></Mouse>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
高阶组件(HOC)
高阶组件
是一个函数
,在函数中有可复用的类组件,通过props将可复用的状态传给被包装组件- 高阶组件内部
创建一个类组件
,在这个类组件中提供复用的状态逻辑代码
,通过prop将复用的状态传递给被包装组件WrappedComponent
高阶组件(HOC)使用步骤:
- 创建一个函数,一般使用
with开头
便于区分
function withMouse() {
}
- 给函数传入一个参数,该参数为将要渲染的组件,因此命名应该以大写字母开头
function withMouse(WrappedComponent) {
}
- 在这个函数内部中创建一个类组件,
提供需要复用的状态和操作状态的方法
并返回
function withMouse(WrappedComponent) {
class Mouse extends React.Component {
}
return Mouse
}
- 在该组件中渲染作为参数传入的组件,并通过展开运算符的方式将复用的状态作为参数组件的属性(将状态通过prop传递给参数组件
function withMouse(WrappedComponent) {
class Mouse extends React.Component {
// Mouse组件的render方法中:
return <WrappedComponent {
...this.state} />}
return Mouse
}
- 调用该高阶组件函数并传入需要增强的组件,即可返回添加复用参数和方法后的组件
// 创建组件
const MousePosition = withMouse(Position)
// 最后渲染组件
<MousePosition />
注意:在使用React调试工具进行调试时,为区分高阶组件得到的组件名称,需要设置displayName来进行区分。
设置displayName
- 使用高阶组件存在的问题:得到的两个组件名称相同
- 原因:默认情况下,React使用
组件名称
作为 displayName- 解决方式:为 高阶组件 设置
displayName
便于调试时区分不同的组件- displayName的作用:用于设置调试信息(React Developer Tools信息)
Mouse.displayName = `WithMouse${
getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
传递props:
- 问题:props丢失
- 原因:高阶组件没有往下传递props
- 解决方式:渲染 WrappedComponent 时,将
state
和this.props
一起传递给组件
传递方式:
<WrappedComponent {
...this.state} {
...this.props} />
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
// 创建高阶组件
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(){
传递props
return <WrappedComponent {
...this.state} {
...this.props}></WrappedComponent>
}
}
// 设置didplayName
Mouse.displayName = `WithMouse${
getDisplayName(WrappedComponent)}`
return Mouse
}
function getDidplayName(WrappedComponent){
return WrappedComponent.getDidplayName || WrappedComponent.name || 'Component'
}
// 用来测试高阶组件
const Position = props=>{
<p>鼠标当前位置:(x:{
props.x},y:{
props.y})</p>
}
const Cat = props=>{
<img
src={
img}
alt=""
style={
{
position:'absolute',
top:props.y-64,
left:props.x-64
}}
/>
}
// 获取增强后的组件
const MousePosition = withMouse(Position)
// 调用高阶组件来增强猫捉老鼠的组件
const MouseCat = withMouse(Cat)
class App extends React.Component{
render(){
return(
<div>
<h1>高阶组件</h1>
{
/* 渲染增强后的组件 */}
<MousePosition a="1"/>
<MouseCat />
</div>
)
}
}
ReactDOM.render(<App />,document.getElementById('root'))