Hooks 体系
系统学习状态、副作用、引用、缓存和并发相关 Hooks。
hooks
⚽ 提示
为什么需要hooks?
- 原来函数组件不行:因为原本的函数组件,每次渲染时,函数都会重新执行(相当于render函数),所以函数内无法保存自己的状态数据,所以需要hooks来实现函数内保存状态。而原本的class component渲染只需要render函数重新执行,所以可以保存状态,同时函数组件也没有生命周期,所以需要hooks来进行补充
- 类组件不行:类组件逻辑复杂,不好理解,this指向不明,成为学习的障碍,组件状态复用困难
❗ 提示
注意事项:
- 只能在函数组件/自定义hook调用hook
- 只能在函数最外层调用hook,不能在函数内块代码(if、for、循环)调用
useState
- 参数-- 初始化值 ,默认为undefined
- 返回值 -- 数组,元素1,当前状态值,元素2 设置状态的函数
import { useState } from 'react'
function Counter(props){
const [ count,setCount ] = useState(0) // 参数-初始化值 ,默认为undefined
// 返回值 -- 数组,元素1,当前状态值,元素2 设置状态的函数
}
useEffect
主要用于副作用代码,默认情况下会在每次组件渲染完成会自动回调,如操作dom/网络请求/事件监听
也就是不会阻塞渲染,内容显示后才会执行这个回调
- 参数 Setup: ()=>cleanUp|undefined,dependence
import { useEffect } from 'react'
function Counter(props){
useEffect(()=>{
// 副作用操作,默认情况下会在渲染完成时执行
// 返回一个清理函数 模拟componentWillUnmount 生命周期
},[])
// 第二个参数是依赖项数组,只有依赖赖项发生改变时重新执行
// 空数组表示仅执行一次 模拟componentDidMount
// 不传表示每次渲染都执行 模拟 componetDidUpdate
}
一个函数组件可以有多个useEffect,按照顺序执行,并可以抽取成自定义hook,按照用途组织代码
❗ 提示
- 渲染时先执行setup,然后存储cleanUp
- 卸载时执行cleaUp
- dependence依赖数组可以控制更新的触发 模拟生命周期: componentWillUnmount: 返回的清理函数 componentDidMount: dependence 空数组,第一次渲染运行 componetDidUpdate: dependence不传参数,每次运行
useContext
用于获取祖先组件注入的上下文
创建上下文
import { createContext } from 'react'
export const userContext = createContext()
注入上下文
import { userContext } from './context'
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './pages/App';
import { Provider } from 'react-redux'
import store from './store'
import { HashRouter } from 'react-router-dom'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<userContext.Provider value={{name:'test'}}>
<Suspense fallback={<h3>loading....</h3>}>
<HashRouter>
<App />
</HashRouter>
</Suspense>
</userContext.Provider>
);
使用useContext获取上下文内容
import React, { memo, useContext } from 'react'
import { userContext } from '../context'
const Hooks = memo(() => {
const user= useContext(userContext)
return (
<div>
<div>Hooks</div>
<div>{user.name}</div>
</div>
)
})
export default Hooks
❗ 提示
- 获取到的上下文需在祖先组件中进行注入
- 上下文发生变化,使用的组件会触发重新渲染
- 注入的组件和使用的组件中间的组件,使用了memo来包裹优化情况下,不会触发重新渲染,也不会影响上下文的传递
useCallback
参数1: fn
参数1: depandences
❗ 提示
useCallback并不能节省函数定义时的性能开销,也就是说每次函数组件重新渲染的时候,重新执行useCallback,虽然返回的是同一个函数,但是作为参数传递的函数,仍然每次都是重新定义了的,这里的性能没有优化, 优化的地方是,对于使用了useCallback返回函数作为props的子组件,不会每次都触发重新渲染(因为函数是相同的) useCallback在两种情况下是有优化的:
- 把一个函数fn传递给memo包裹的子组件,在子组件中使用fn,这时可以避免子组件每次都渲染
- 在别的hook中使用了fn,fn作为一个依赖项 总结来说: 当一个函数fn,作为另一个地方的依赖项时,为了避免另一个地方每次都被重新渲染,就要使用useCallback缓存该函数fn 如果每个函数都使用useCallback,会导致处理hook的逻辑变多,属于反向优化
使用
import React, { memo, useCallback, useState } from 'react'
const SubmitBtn = memo((props) => {
const { handleSubmit } = props
return (
<div onClick={handleSubmit}>+1</div>
)
})
const Hooks = memo(() => {
const [count,setCount ] = useState()
const handleSubmit = useCallback(() => {
console.log('handleSubmit')
setCount(count+1)
}, [count])
return (
<div>
<div>Hooks</div>
<SubmitBtn handleSubmit={handleSubmit}></SubmitBtn>
</div>
)
})
export default Hooks
使用ref 进一步优化
useRef返回一个固定的对象,但是其current值可被每次更新,这样可以在回调函数中每次获取最新的值,避免闭包陷阱以及生成新的函数,就不会不会每次更新子组件
import React, { memo, useCallback, useContext, useRef, useState } from 'react'
const SubmitBtn = memo((props) => {
const { handleSubmit } = props
return (
<div onClick={handleSubmit}>+1</div>
)
})
const Hooks = memo(() => {
const [count, setCount] = useState()
const countRef = useRef()
countRef.current = count
const handleSubmit = useCallback(() => {
setCount(countRef.current + 1)
}, [])
return (
<div>
<div>Hooks</div>
<SubmitBtn handleSubmit={handleSubmit}></SubmitBtn>
</div>
)
})
export default Hooks
❗ 提示
这里要注意【闭包陷阱】 如果没有传入依赖参数【上面代码中的 [count] 】 当传入的fn,依赖了某个外部变量时(上面代码中的count),由于useCallback的缓存作用,每次返回的都是第一次运行时传入的函数,也就是说,count取到的值也是第一次运行的值 (形成了闭包时,取到的count就是固定的) 如有传入依赖,依赖改变时,useCallback会返回一个新的函数,也就以当前的count值,生成一个新的闭包
useMemo
const value = useMemo(fn, dependencies)
// value 是 fn的执行结果,dependencies不变时,缓存fn的执行结果,
可以使用useMemo实现useCallback
const useCallback = (fn, dependencies) => {
return useMemo(() => fn, dependencies)
}
❗ 提示
useMemo优化的点:
- 避免fn每次渲染都被重新执行,优化的是本组件fn执行的开销 -- 主要用途
- 当需要传递对象类型的数据给子组件时,避免每次重新渲染定义新的对象导致子组件被重新渲染,这个和useState重复了
useRef
组件的生命周期中,对象是的内存地址不变,主要用途是用来获取dom
import React, { memo, useRef} from 'react'
const Hooks = memo(() => {
const countRef = useRef()
return (
<div>
<div ref={countRef}>Hooks</div>
</div>
)
})
export default Hooks
useLayoutEffect
在内容更新到屏幕上前执行,会阻塞渲染。比useEffect执行时机更前一点
自定义hook
多个组件相同的的逻辑,可以单独抽取出来,实现代码复用
useSelector 和 useDispatch
redux中的hook
useDispatch获取redux中的store的dispatch函数,仅此而已
useSelector的回调函数相当于原来connect高阶函数的 mapStateToProps
useSelector有2个参数,第一个参数是mapStateToProps,第二个参数是新旧state的比较方法,决定是否更新
import React, { memo } from 'react'
import { useDispatch, useSelector,shallowEqual } from 'react-redux'
import { addNumber } from '../store/reducer'
const Hooks = memo(() => {
const { count } = useSelector((state) => ({ count: state.home.count }),shallowEqual)
const dispatch = useDispatch()
function addCount(num) {
dispatch(addNumber(num))
}
return (
<div>
<div> count: {count}</div>
<button onClick={e => addCount(1)}>+</button>
</div>
)
})
export default Hooks
❗ 提示
性能优化 默认情况下不传入第2个参数,会导致useSelector监听整个store的变化 这个时候传入第二个参数,可以浅层比较前后两个state是否变化,从而决定是否更新当前的组件
useTransition
可以降低某些操作的优先级
const [pending,startTransition] = useTransition()
startTransition(()=>{
})
pending,表示回调函数是否在执行,startTransition里的回调函数是否已经执行完成
startTransition的回调函数会被降低优先级,在其他更新完成后才去调用,避免一些耗时操作去影响界面渲染的更新