🌈个人主页:前端青山 🔥系列专栏:React篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来React篇专栏内容:React-Hooks
目录
1、简介
2、hook的使用限制
3、常用的hook函数
3.1、useState
3.2、useEffect
3.3、useRef
3.4、useContext
3.5、useReducer
3.6、useMemo
3.7、useCallback
3.8、redux相关
3.9、react-route-dom相关
3.10、ahooks
3.11、自定义hook
1、简介
React中组件由函数组件与类组件,在 React Hooks 出现之前,我们可以使用函数和类组件来进行项目开发,但是如果组件中需要进行状态管理,函数组件就显得无能为力。React在v16.8 的版本中推出了 React Hooks 新特性,Hook是一套工具函数的集合,它增强了函数组件的功能,hook不等于函数组件,所有的hook函数都是以use开头。【以use开头的特殊的函数集合,就称之为Hooks】
Hook的定义:是react函数组件中使用的一些以“use”开头的特殊函数。
使用 React Hooks 相比于从前的类组件有以下几点好处:
- 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
- 组件树层级变浅,代码的跳跃性会变小,在原本的代码中,我们经常使用 HOC/render/Props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现
- hook使用比使用类组件简单许多(仁者见仁智者见智,但是Redux使用时 ,绝对是函数组件简单)
2、hook的使用限制
- hook只能用在函数组件中(被导出的函数里面),class组件不行
- 普通(非组件导出函数,且不以use开头的函数)函数不能使用hook(hook不能在组件函数外去使用)
- hook不能被有条件的调用,因此不能放在if/for中(如果真有有条件调用的需求,请把条件写在hook函数内)
3、常用的hook函数
官网文档:Hooks API Reference – React
hook的使用步骤:
- 自定义hook(自己用自己写的)
- 导入hook成员
- 使用hook成员
3.1、useState
作用:保存组件的状态(用于解决函数组件无状态问题)
语法:
const [state, setstate] = useState(initialState);
案例:
/**
* hook:useState
* 作用:实现函数组件中的所谓的“状态”
* 提供方:react
* 语法:const [访问变量名,设置数据方法名] = useState(初始默认值)
* 参数含义:
* 访问变量名:可以通过该变量名获取state的值
* 设置数据方法名:该方法可以用于修改state的值,其只接受一个参数,参数要么是直接表示其值,要么以函数返回形式表示其值。如果前面的变量名叫a,那么此处的函数名一般叫做setA
* 初始默认值:给state的初始值,支持字面量,也支持数组和对象等复杂数据类型
* 注意点:
* a. 在修改字面量时,直接将最终的字面量的值给到修改函数即可
* b. 在修改对象或数组的时候,如果只是修改其中的一部分数据,记得最后返回的数据需要携带没有被修改的数据,如果不携带则丢失
*/
import React, { useState } from 'react';
const Index = () => {
// 基础的字面量
const [count, setCount] = useState(0)
// 对象类型
const [user, setUser] = useState({ uname: "张三", age: 22 })
// 修改用户的信息的事件处理程序
const modUser = () => {
setUser({ ...user, age: 33 })
}
return (
<div>
<div>计数器的值是:{count}</div>
<div><button onClick={() => setCount(count + 1)}>+1</button></div>
<hr />
<div>
<div>用户信息是:</div>
<div>
用户名是:{user.uname}
</div>
<div>
年龄是:{user.age}
</div>
<button onClick={() => modUser()}>修改用户年龄为33</button>
</div>
</div>
);
}
export default Index;
3.2、useEffect
作用:模拟类组件中的生命周期的
函数组件对于在一些生命周期中操作还是无能为力,所以 React提供了 useEffect 来帮助开发者处理函数组件,来帮助模拟完成一部份的开发中非常常用的生命周期方法(并不是全部的生命周期)。常被称为:副作用处理函数。此函数的操作是异步的。
useEffect 相当类组件中的3个生命周期 (但含义上不完全等同)
- componentDidMount
- componentDidUpdate
- componetWillUnMount
语法:
useEffect(() => { effect... 副作用的操作,(类似于组件挂载完毕后、更新完毕后的操作) return () => { cleanup... 清理副作用 (类似于组件的解除挂载的周期,可选) } },[INPUT,....])
案例:
/**
* hook:useEffect
* 作用:用于操作作用及副作用代码的(用于控制函数组件生命周期的)
* 语法:
* useEffect(callback[,array])
* 提供方:react
* 细致语法:
* a. 在首次渲染及后续每次更新之后会执行callback的代码(模拟首次componentDidMount及componentDidUpdate)
* useEffect(没有返回值的callback)
* b. 被返回的函数是用于清理副作用的,会在执行下一次副作用前执行
* useEffect(带有返回值的cakkbacl) // 返回值必须是一个函数
* c. 有第二个参数,并且第二参数为空数组(仅仅会让副作用代码在首次挂载完毕后执行一次)
* useEffect(callback,[])
* d. 有第二个参数,且第二个参数中有具体的元素,元素个数不限,元素为useState的访问变量名。关注指定元素的更新,当被指定的元素更新后,将会再去执行副作用代码,及清理副作用的代码(如有)。
* useEffect(callback,[el1,el2,....])
*
* 清理副作用很有意义:
* 假设给页面绑定了点击事件,如果不清理事件,可能会导致事件越绑越多
*/
import React, { useEffect, useState } from 'react';
const Index = () => {
const [state, setState] = useState(0)
const [state1, setState1] = useState(100)
const [pos,setPos] = useState({x:0,y:0})
const handler = (e) => {
console.log('点了页面');
// console.log(e);
setPos({x: e.clientX,y: e.clientY})
}
// * 清理副作用很有意义:
// * 假设给页面绑定了点击事件,如果不清理事件,可能会导致事件越绑越多
useEffect(() => {
document.addEventListener("click",handler)//1
return () => {
document.removeEventListener("click",handler)//2
}
})
// 情况1:没有返回值的callback
// useEffect(() => {
// console.log('1');
// })
// 情况2:带有函数返回值的callback
// useEffect(() => {
// console.log(1); // 副作用代码
// return () => {
// console.log(2); // 清理副作用
// }
// })
// 情况3:第二参数为空数组
// useEffect(() => {
// console.log(1); // 副作用代码
// // return () => { // 如果第二参数给了空数组,则return的函数将无意义
// // console.log(2); // 清理副作用
// // }
// }, [])
// 情况4:第二参数有元素
// useEffect(() => {
// console.log(1); // 副作用代码
// return () => {
// console.log(2); // 清理副作用
// }
// }, [state])
return (
<div>
<div>当前state是:{state}</div>
<div><button onClick={() => setState(state + 1)}>+1</button></div>
<hr />
<div>当前state1是:{state1}</div>
<div><button onClick={() => setState1(state1 + 1)}>+1</button></div>
</div>
);
}
export default Index;
3.3、useRef
作用:用来生成对 DOM 对象的引用(类似于类组件中的createRef方法)
案例:
- 父组件Index.jsx
/**
* useRef的注意点:
* 1. 使用肯定得导入useRef
* 2. 通过执行该useRef函数获取ref对象
* 3. 将ref对象挂载到需要获取对象的标签上面,例如:ref={ref}
* 4. 后续我们可以通过ref对象获取对应的标签的对象
* 5. 特别需要注意:默认情况下,普通的html标签是可以直接使用ref对象的(获取到的是dom对象),而组件标签默认不能使用ref对象!
* 6. 如果需要解决函数组件标签使用ref的问题,需要借助React.forwardRef()【让子具备转发ref的能力,子去给父ref】,该方法不是hook函数,而是hoc强化函数;还需要借助一个hook函数:useImperativeHandle来告诉父可以从子这里获取哪些数据或者方法(向外暴露成员)。
* 7. useImperativeHandle函数的语法:useImperativeHandle(ref对象,callback),callback必须返回一个对象,对象即为向外暴露的成员。其中ref对象为函数组件函数的第二形参
- 子组件Child.jsx
import React, { useState, forwardRef, useImperativeHandle } from "react"
const Child = (props, ref) => {
const [state, setState] = useState(66666)
const showMsg = () => {
console.log(state)
}
// 向外暴露成员方法、变量
useImperativeHandle(ref, () => ({ showMsg }))
return (
<div>
<div>这是子组件</div>
</div>
)
}
// 通过forwardRef强化当前的函数组件,使其具备转发ref的能力
export default forwardRef(Child)
面试题:
- useRef能否用createRef替换?【可以】
- useRef与之前学习的createRef有何区别?【useRef在多次渲染页面的时候始终是同一个ref对象的引用;而createRef在每次更新后都会产生新的ref引用】
3.4、useContext
- 与类组件相比,实现同等效果,函数组件比较简单
作用:createContext实现数据的共享,只是消费数据的方式不一样
案例:
- context对象产生的文件createContext.js
import { createContext } from "react"
// createContext函数是可以传递参数的。
// 参数的意义可以理解成是待消费的数据的默认值
// 默认值一般在项目做单元(组件)测试(unit test)的时候可能会被用上
export default createContext({ a: 11, b: 22, c: 33 })
- 售卖方组件
/**
* 1. 售卖方提供需要需要借助context对象的Provider组件
* 2. 通过售卖方Provider组件的value属性将需要共享的数据传递给子组件
* 3. 消费方通过useContext函数进行消费,语法:const data = useContext(context对象)
*/
import React from "react"
import Consumer from "./Consumer"
import Obj from "./createContext"
// 获取售卖方的身份
const { Provider } = Obj
const ProviderCmp = () => {
const data = { a: 1111, b: 2222, c: 3333 }
return (
<div>
<Provider value={data}>
<Consumer />
</Provider>
</div>
)
}
export default ProviderCmp
- 消费方的组件
import React, { useContext } from "react"
import Obj from "./createContext"
const Consumer = () => {
// 通过useContext消费数据
const data = useContext(Obj)
return (
<div>
<div>a的值是:{data.a}</div>
<div>b的值是:{data.b}</div>
<div>c的值是:{data.c}</div>
</div>
)
}
export default Consumer
3.5、useReducer
在组件内添加一个reducer方法,管理组件的状态
import React, { useState, useReducer } from 'react'
export default function More() {
const [count, setCount] = useState(1)
const initialState = {
count: 100
}
const reducer = (state, action) => {
switch (action.type) {
case 'add':
return { count: state.count + action.payload }
default:
return state
}
}
// state对应initailState dipatch派发方法 reducer定义纯函数修改state
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
More
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
<div>
<button onClick={() => dispatch({ type: 'add', payload: 100 })}>
{state.count}
</button>
</div>
</div>
)
}
3.6、useMemo
React.memo 为高阶组件。如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
如果组件在props相同的情况下,渲染结果相同,那么就可以使用memo来进行优化
import React, { useState, memo } from 'react'
/***
* memo react中提供的高阶组件
* 高阶组件 参数为组件 返回一个新组件
*/
export default function App() {
const [num, setNum] = useState(100)
return (
<div>
App
<div>
<button onClick={() => setNum(num + 1)}>{num}</button>
</div>
{/* <Child></Child> */}
{/* 注意如果通过memo缓存的组件 它的状态[state,props]发生改变 也会重新渲染 */}
<Child data={num}></Child>
</div>
)
}
// 父组件更新导致子组件也进行渲染 实际子组件没有发生变化 造成渲染的性能浪费
// memo(函数组件,回到函数[返回值bool])
const Child = memo(
(props) => {
console.log('子组件被渲染了')
return <div>Child</div>
},
(prevProps, nextProps) => {
// true不渲染
// false渲染
// console.log(nextProps);
if (nextProps.data % 2 === 0) {
return false
} else {
return true
}
}
)
// 使用memo缓存组件
// Child = memo(Child)
useMemo 是 React 中的一个钩子,它可以帮助你避免在不必要的情况下重新渲染组件。它通过对比当前状态和前一个状态,决定是否重新计算或记忆一个值。 接收两个参数,第一个参数是个函数,第二个是依赖项。返回一个memoized值,只有当它的某个依赖项改变时才重新计算 memoized 值,初始化的时候也会调用一次,这种优化有助于避免在每次渲染时都进行高开销的计算
import React, { useState } from 'react'
import { useMemo } from 'react'
export default function App() {
const [num, setNum] = useState(10)
const [count, setCount] = useState(10)
const doubleNum = useMemo(()=>{
console.log('计算num的双倍');
return num*2
},[num])
return (
<div>App
<button onClick={()=>setNum(num+1)}>{num}</button>
<div>double:{doubleNum}</div>
<div>
<button onClick={()=>setCount(count-1)}>{count}</button>
</div>
</div>
)
}
3.7、useCallback
React 的 useCallback 是一个 Hook,用于优化性能,在组件的更新过程中避免不必要的函数重新定义。 在 React 中,当组件重新渲染时,它会重新执行所有的函数,因此在频繁更新的组件中使用多个函数会导致不必要的性能开销。useCallback 可以解决这个问题,它接受两个参数:一个回调函数和一个依赖项列表。当依赖项列表中的任意一项改变时,useCallback 会重新定义回调函数,否则它会返回一个缓存的函数引用,从而避免不必要的函数重新定义。 使用 useCallback 可以显著提高组件的性能,特别是在处理高频率的更新或大量的交互操作时。但是,请注意,如果使用不当,它也可能导致代码变得难以理解和维护。因此,在使用 useCallback 时,需要谨慎考虑其影响,并确保代码可读性和可维护性
import React, { useState, useCallback } from 'react'
export default function App() {
const [num, setNum] = useState(10)
const [count, setCount] = useState(100)
// 每次组件渲染时,add方法会被重新定义 造成不必要的开销
// const add = () => {
// }
// 使用useCallback缓存函数 函数的引用
const add = useCallback(() => {
setCount(count + 1)
}, [count])
return (
<div>
<div>
<button onClick={() => setNum(num + 1)}>{num}</button>
</div>
<div>
<button onClick={add}>{count}</button>
</div>
</div>
)
}
3.8、redux相关
- 与类组件相比,函数组件的redux使用非常简单(只是简化了组件中的使用包括获取数据与action的派发)
import { useDispatch, useSelector } from "react-redux";
注意点:官方自带的useReducer
hook也可以实现针对reudx的操作,但是企业一般不直接用它。而是使用react-redux中提供的封装过的hook(useSelector,useDispatch)。如果企业项目有自己封装redux相关的hook的时候才会使用useReducer
。其实react-redux提供的自定义的hook底层也是基于useReducer
实现的。
作用:
- useSelector:帮助我们获取仓库中的数据,参数是callback,函数有一形参state(默认数据源)
- useDispatch:帮助我们派发用于修改的action
案例:通过useSelector,useDispatch实现对于redux中数据的读写(计数器案例)
/**
* 1. 关于redux的hooks分为两派:官方的useReducer、react-redux提供的useSelector/useDispatch。【建议用后者】
* 2. useSelector作用:获取仓库中的数据,语法:const state = useSelector(callback),函数接收形参,形参为仓库中的全部的数据,函数返回的为处理好的数据
* 3. useDispatch作用:返回一个dispatch方法
*/
import React from "react"
import { useSelector, useDispatch } from "react-redux"
const Index = () => {
// 获取数据
const state = useSelector(state => state)
// 产生dispatch方法
const dispatch = useDispatch()
// 事件处理程序
const add = () => {
// 派发action
dispatch({ type: "add", payload: 1 })
}
return (
<div>
<div>计数器的值:{state.count}</div>
<button onClick={add}>+1</button>
</div>
)
}
export default Index
3.9、react-route-dom相关
版本:v5.3
import { useHistory, useParams, useLocation } from "react-router-dom";
作用:快速获取路由信息的
- useHistory:获取history路由信息对象
- useParams:获取路由中动态路由参数对象
- useLocation:获取路由中的location对象信息
案例:
/**
* hook:路由包提供
* useLocation/useHistory/useParams
* 作用:操作路由信息
*/
import React from 'react';
import { useHistory, useLocation, useParams } from "react-router-dom"
const Index = () => {
// 等同于类中: const his = this.props.history
const his = useHistory()
// 等同于类中: const loc = this.props.location
const loc = useLocation()
// 等同于类中: const par = this.props.macth.params
const par = useParams()
console.log(his, loc, par);
return (
<div>
<button onClick={() => his.push("/news")}>去往“/news”</button>
</div>
);
}
// 此处不需要withRouter
export default Index;
注意点:用了这个三个hook,组件在导出的时候就不用再withRouter。
3.10、ahooks
一套高质量可靠的 React Hooks 库
安装
npm install --save ahooks
根据文档示例,调用对应hook实现其功能
import React, { useRef } from 'react'
import { useFullscreen } from 'ahooks'
export default () => {
const ref = useRef(null)
// isFullscreen检测当前是否为全屏状态
// enterFullscreen进入全屏
const [isFullscreen, { enterFullscreen, exitFullscreen, toggleFullscreen }] =
useFullscreen(ref)
console.log(isFullscreen)
return (
<div ref={ref} style={{ background: 'white' }}>
<button onClick={enterFullscreen}>全屏</button>
<button onClick={exitFullscreen}>退出全屏</button>
<button onClick={toggleFullscreen}>切换屏幕</button>
</div>
)
}
import React from 'react'
import { useNetwork } from 'ahooks'
export default function App() {
const networkInfo = useNetwork()
console.log(networkInfo)
const { online } = networkInfo
return (
<div>
App
{online ? (
<div style={{ color: 'green', fontSize: 30 }}>●</div>
) : (
<div style={{ color: 'red', fontSize: 30 }}>●</div>
)}
</div>
)
}
3.11、自定义hook
案例:自定义在线状态hook,要求使用了这个hook可以
自动判断当前网络连接的情况
应用场景:在线聊天类型的项目,可以用这个hook动态判断当前用户的网络连接状态
实现代码:
/**
* 自定义hook = 自定义函数
* 1. 名称必须以use开头
* 2. 细节:
* a. 得有返回值,返回当前用户的网络状态
* b. 是否在线的状态通过navigator.onLine进行判断
* c. 由于需要持续获取用户的状态,得用到事件监听,监听事件是什么?
* - online
* - offline
* d. 刚进入到这个函数的时候需要初始化一个变量去存储当前的网络状态
*
*/
import { useState, useEffect } from "react"
function useOnline() {
// 立马获取当前的状态
const [online, setOnline] = useState(navigator.onLine)
// .....
const on = () => setOnline(true)
const off = () => setOnline(false)
useEffect(() => {
// 执行副作用
window.addEventListener("online", on)
window.addEventListener("offline", off)
return () => {
// 清理副作用
window.removeEventListener("online", on)
window.removeEventListener("offline", off)
}
})
// 返回
return online
}
export default useOnline