react高阶组件和hooks

本文详细介绍了React中的高阶组件(HOC)的概念、使用场景、实现方式及渲染劫持,包括属性代理和反向继承两种实现方法。同时,讲解了Hooks的基本概念、用途,如何在函数式组件中使用useState和useEffect来模拟状态管理和生命周期。最后,讨论了在React Router 6.0中如何封装自定义的withRouter高阶组件。

1. react高阶组件

1.1 高阶组件的概念

高阶组件(Higher Order Component,简称:HOC ): 是 React 中用于重用组件逻辑的高级技术, 它本身不是react中的组件, 而是一个函数, 这个函数接受一个react组件作为参数,并返回一个新组件, 实现了对原有组件的增强和优化, 可以对原有组件中的state, props和逻辑执行增删改操作, 一般用于代码重用和组件增强优化

概述:高阶组件是一个函数,而不是组件,但其参数和返回值都是组件

1.2 高阶组件的使用场景

1.2.1 需要代码重用时

react如果有多个组件都用到同一端逻辑,这是就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中,从而减少代码的逻辑重复。

1.2.2 需要组件增强优化时

比如我们在项目中使用的组件有些不是自己写的,而是从网上撸下来的,但是第三方写的组件可能比较复杂,有时不能完全满足需求,且第三方组件不易修改,此时也可以用高阶组件。在不修改原始组件的前提下,对组件添加满足实际开发需求的功能。

一般来说,高阶组件在的父组件,会对原始组件模板做增强优化

return (
    <div>
      <nav><h3>这是高阶组件增加的导航栏</h3></nav>
      <OldCom/>
    </div>
  )

1.3 高阶组件的实现方式

1.3.1 属性代理

流程:通过创建新组件来包裹原始组件,把原始组件作为新组件的子组件渲染

功能:可实现对原始组件的 props数据更新组件模板更新

示例代码如下:

import React, { Component } from 'react'
function myHoc(OldCom){
  return class NewCom extends Component {
    constructor(props){
      super(props);
      console.log(props);  // 路由信息会传入高阶组件的props中,可以对它进行增删改
    }
    render() {
       return (
// 如果返回一个新模板,相当于把原始组件直接替换了,这就是渲染劫持
          return <div><h1>这是订单页的高阶组件</h1></div>

// 如果要返回原始组件模板,把原始组件作为子组件返回即可
          // return <OldCom/>
      )
    }
  }
}
export default myHoc  // 导出高阶组件这个函数

注意:在构造器中路由信息会传入高阶组件的props中,可以对它进行增删改

由于props是只读的,不能修改,要对其进行深复制,然后再进行修改

有两种深复制方式如下:

// this.tempProps = JSON.parse(JSON.stringify(props))
  • 第一种深复制会丢失函数,因为JSON里面不识别函数,所以不能放函数
this.tempProps = {...props}
  • 第二种深复制不会丢失函数,但只能深复制第一层。

此处使用第二种方式,对props进行增删改操作:

this.tempProps.name = "张三"
console.log(this.tempProps);
delete this.tempProps.match  // ES5 删除属性 
var { location, ...tempObj} = this.tempProps  // ES6 解构删除
this.tempProps = tempObj
console.log(this.tempProps);
this.tempProps.history.action = "PUSH"

在这里只能操作props,不能操作state,无法调用原始组件的state,如果需要修改state数据,请使用 反向继承 实现

在render()渲染函数中,如果需要修改props,需要传入修改后的深复制品

return (
    <div>
      <nav><h3>这是高阶组件增加的导航栏</h3></nav>
      <OldCom {...this.tempProps}/>
    </div>
)

在高阶组件中还可以做登录验证,类似与vue中的路由守卫。

问题:由于路由中配置的是高阶组件,所以路由信息传入到高阶组件中,原始组件中就没有路由信息了,怎么解决?
  • 因为此时原始组件中已经没有路由信息了,即没有登录状态,可通过登录状态做一个if判断。若无登录状态,则返回空,并在100秒后跳转到登录页,若有登录状态,则返回原始组件模板页面。
console.log("orderHoc", this.props);
if(this.isLogin){
	return <OldCom/>
}else{
    setTimeout(()=>{
      this.props.history.push("/login")
    }, 100)
    return ""
}
  • 此时需要在render()函数的返回的原始组件中,动态拼接this.props即可传入路由信息
return (
    <div>
      <nav><h3>这是高阶组件增加的导航栏</h3></nav>
      <OldCom {...this.props}/>
    </div>
)

1.3.2 反向继承

流程:通过创建新组件继承自原始组件类,把新组件作为子类,原始组件作为父类

功能:可实现对原始组件的 state状态数据更新组件模板更新

注意:反向继承中,新组件和原始组件要求必须都是类组件

示例代码如下:

import React, { Component } from 'react'
function myHoc(OldCom){
  return class NewCom extends OldCom{
    constructor(props){
      super(props);
   // 因为当前组件类继承于原始组件类,子类可以直接调用父类的数据
      console.log("myHoc2", this.state, this.add);
	  // this.setState({  // 此时未能渲染数据
      //   count: 100
      // })
    }
    render(){
      return <div>
        <nav><h3>这是高阶组件中的导航栏</h3></nav>
        {super.render()}
      </div>
    }
  }
}
export default myHoc

反向继承中,不能返回原始组件标签,因为原始组件是父类,不能作为子组件渲染

return <OldCom/>  // 错误写法

需要使用super调用父类的render渲染函数,渲染父类模板

return super.render()

需要在组件渲染之后更新state数据

componentDidMount = () => {
  this.setState({
    count: 100,
    age: 20 
  }, ()=>{
    this.add()
  })
}
  • this.setState()为异步更新,可在其回调的箭头函数中调用函数
  • 而在构造器中只能调用读取state数据,不能调用setState更新

1.4 高阶组件的渲染劫持

高阶组件的渲染劫持:通过高阶组件把原始组件的模板进行修改和替换

1.4.1 渲染劫持概念

渲染劫持指对一个组件渲染内容的装饰或修改, 一般通过高阶组件来实现, 把一个组件传入高阶组件, 可以对这个组件的模板进行修改后执行渲染, 也可以阻止组件渲染, 并修改组件中的数据和逻辑

1.4.2 渲染劫持的应用

一般用于一些需要登录状态的页面, 在路由请求渲染页面(如订单页)之前, 使用高阶组件判断是否已登录, 如果已登录, 返回订单页模板, 如果没有登录, 返回空, 并跳转到登录页

1.5 高阶组件的实现步骤

1.5.1 新建高阶组件文件 MyHOC.jsx

1.5.2 在文件中创建函数

  • 函数的参数是一个组件OldCom, 函数的返回值也是一个组件 NewCom
function myHoc(OldCom){
  return class NewCom extends React.Component{
    render(){
        let newProps = { age: 10, sex: '男' }
        return (
          <OldCom {...newProps} ></OldCom>
        )
    }
  }
}

属性代理(上)或者(反向继承)

function myHoc (OldCom){
  return class NewCom extends OldCom{
    componentDidMount() {
      this.setState({ name: '李四' })
    }
    render() {
      return super.render()
    }
  }
}

1.5.3 导出高阶组件函数

export default myHoc

1.5.4 在需要使用高阶组件的原始组件中导入

import MyHOC1 from "./OrderHOC1"  // 导入属性代理高阶组件
import MyHOC2 from "./OrderHOC2"  // 导入反向继承高阶组件

1.5.5 在导出组件时,使用高阶组件处理之后,再导出

export default MyHOC1(Order)   // 属性代理高阶组件处理
// export default MyHOC2(Order) // 反向继承高阶组件处理

原始组件示例代码:

// 这是定义原始组件的页面
import React, { Component } from 'react'
import MyHOC1 from "./OrderHOC1"
import MyHOC2 from "./OrderHOC2"

class Order extends Component {
  state = { count: 0 }
  add = ()=>{
    this.setState({
      count: this.state.count + 1
    })
  }
  render() {
    console.log("order", this.state, this.props);
    return (
      <div>
        <h1>订单页</h1>
        <h2>
          {this.state.count}
          <button onClick={this.add}>+</button>
        </h2>
      </div>
    )
  }
}
export default MyHOC1(Order)
// export default MyHOC2(Order)

2. hooks

hooks语法只适用于函数式组件中,不能用于类组件

2.1 什么是hooks?

hooks是react新版本提供的组合式API语法,类似于vue3组合式API(也叫hooks)

2.2 hooks有什么用?

使函数式组件拥有组件状态和生命周期功能

提供运行效率

避免this指向问题(可以和vue3一样)

2.3 在函数式组件中, 使用hooks语法模拟状态数据的步骤

2.3.1 从react中导入语法函数useState

import React, { useState } from "react"

2.3.2 在函数式组件中, 使用useState创建状态数据

使用useState创建状态数据,参数是默认值,返回值是 数组

数组中第一个值是状态数据的变量名,第二个值是自定义的更新函数,调用更新函数会刷新视图

有三种类型:

  1. 直接定义单个变量
const [count, setCount] = useState(100)
  1. 定义多个变量使用对象结构
const [state, setState] = useState({  
    age: 10,
    name: "张三",
    roomList: []
})
  1. 定义数组
const [arr, setArr] = useState([1, 2, 3])

2.3.3 在组件模板中, 直接调用状态名即可{count}

2.3.4 使用useState函数返回的更新函数,修改状态值setCount(count + 1)

  • 调用更新函数,更新状态,参数是最新值,修改后自动刷新界面
  1. 渲染单数据的写法
<button onClick={()=>{
    setCount(count + 1)  
  }}>{count}</button>
  1. 渲染对象数据的写法
<button onClick={()=>{
    state.age ++
    // setState(state)  // 错误写法
    setState({ 
      ...state
    })
  }}>{state.name}: {state.age}
  {  // 渲染斗鱼数据
    state.roomList.map(item=>{
      return <div key={item.room_id}>
        <img src={item.room_src} alt="" />
      </div>
    })
  }
</button>
  • 修改对象中的状态数据时可以先修改,再拼接上对应的状态名
  1. 使用useState渲染数组中数据的写法
<button onClick={()=>{
    arr[1] ++
    // setArr(arr)  // 错误写法,不会更新视图
    setArr([...arr])
}}>{arr}</button>
  • 注意:useState定义的引用类型数据,更新时,需要修改数据的内存地址(深拷贝/深复制),才会更新视图

2.4 使用hooks中的useEffect函数实现函数式组件的生命周期

默认第一个参数是回调函数,当组件初始化完成和状态更新时调用,类似于类组件中的render()渲染函数。

第二个参数可以控制回调的调用时机,是一个数组,可选,数组中是状态名,指定那些状态值更新会触发回调函数

有三种实现方法:

  1. 监听所有的数据更新
  • 如果不加第二个参数,初始化时调用,任何状态更新都会调用
useEffect(()=>{
    console.log("组件初始化,或有状态更新ComponentWillUpdate")
})  
  1. 只在初始化时监听调用
  • 如果第二个参数是空数组,则只在初始化时调用,状态更新时不会调用
useEffect(()=>{
    console.log("组件初始化")
}, [])
  • []表示只在初始化时调用,相当于生命周期componentDidMount

可以在这里请求数据,如请求斗鱼数据:

axios.get("/api/RoomApi/live", {
  page: 1
}).then(res=>{
  console.log(res.data.data);
  setState({
    age: 25,
    name: "计科",
    roomList: res.data.data
  })
})

注意:请求数据需要写在useEffect函数的回调中,而[]只在初始化时调用,所以不能写在[]中

  1. 只监听某些数据的更新
  • 如果第二个参数数组中有状态名, 则只会在数组中的状态更新时调用
 useEffect(()=>{
    console.log("组件初始化或count或arr更新")
}, [count, arr])

useEffect()函数的第二个参数是数组时,若里边写状态数据名,则是指定哪些数据更新时可执行回调

3.在react路由6.0中封装withRouter高阶组件

react-router6.0版本废弃了组件内的props路由信息( history, location, match ),6.0之后的版本,组件内默认都没有路由信息,包括withRouter高阶组件也被废弃了,需要使用hooks组合式API导入路由信息。

在react-router6.0中所有的组件都没有路由信息,也没有withRouter,需要使用hooks语法引入路由信息(history, location, match),所以我们可以自己封装一个withRouter高阶组件。

3.1 新建自己封装的withRouter函数文件withRouter.jsx

由于路由数据是在props中,所以使用属性代理方式,实现自封装的高阶组件。

3.2 在文件中导入自定义路由信息

import { useHistory, useLocation, useRouteMatch} from 'react-router-dom'

3.3 创建函数式组件,使用hooks语法

function myHoc(OldCom){
  return ()=>{
    // 返回组件模板
    return <OldCom {...tempProps}/>
  }
}

3.4 在返回的箭头函数中,使用hooks提供的组合式API,获取路由信息

const history = useHistory()
const location = useLocation()
const match = useRouteMatch()

3.5 返回组件模板

return <OldCom {...tempProps}/>

3.6 导出封装的高阶组件函数

export default myHoc

3.7 在根组件App.js中导入自己封装的withrouter高阶组件

当使用了hooks新语法,还想使用类组件,就可以使用自己封装的高阶组件来实现。

import withRouter from "./pages/Hooks/withRouter"; 

3.8 在导出根组件时,使用封装的withRouter包裹组件即可传入路由信息

export default withRouter(App);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值