什么是hook
- hook都是一些以use开头的函数,表示钩子
- React 官网提供了 些内置的 hook:ruseState、 useEffect、useMemo、useCallback…
- 允许自定义 hook
- hook使用规则:
- 只能用于函数组件
- 函数组件中使用
- 自定义hook中使用
- 需要在函数组件的顶层调用,不能在if、for、嵌套中使用
- 需要以use开头
- 只能用于函数组件
useState,用于勾出state数据
- 在修改组件外侧的变量(非修改state、prop或强制更新)时,组件并不会更新,所以需要state数据
- 语法:const [state,setState] = useState(initialState)
- initialState 一个可选的初始state数据
- initialState 可以是一个数值,字符串,数组或通过函数返回初始值
- 返回值是一个数组的格式
- 数组第一项 state 就是勾出的state数据
- 组件第一次执行时,state的值为initialState的值
- 组件后续的更新执行时,state的值为上一次setState()修改的值
- 数组第二项setState表示修改这份state数据的一个函数
- 数组第一项 state 就是勾出的state数据
- setState()可以是一个新state,也可以是一个函数来返回一个新state
setNum(num+1)
setNum(num+1)
setNum(num+1)
- 在想要通过这种方式进行多次叠加时,结果并不会叠加,他们的num每次都是原num,因为只有在所有setState走完之后才会更新组件,state才会更新
- 这需要通过函数的形式
setState((prev)=>prev+1)
setState((prev)=>prev+1)
setState((prev)=>prev+1)
这种方式,函数内的prev参数就是上一次修改后的state数据,才能实现叠加
Immer库使用,useImmer()
- 当你的useState中的数据是一个多层嵌套的对象,想要修改更新他会变得麻烦,你要一层一层展开到你要修改的部位
- 使用useImmer来代替useState,步骤如下:
- 1.安装use-immer:npm install use-immer
- 2.引入:import { useImmer } from ‘use-immer’
- 3.用useImmer来勾出state数据:
const [state,setState] = useImmer({
msg1:'一层',
obj1:{
msg2:'二层',
obj2:{
msg3:'三层',
obj3:{
msg4:'四层',
name:'张三',
age:18
}
}
}
})
-
- 4.更新数据,让react重新渲染:在事件函数中,setState(draft=>{draft.obj1.obj2.obj3.name=‘李四’;draft.obj1.obj2.obj3.age+=1})
- 通过这样的方式,useImmer会在不影响未修改的值的情况下生成新的对象,来让react更新渲染,避免了深层嵌套时多次展开的重复代码
- 如果useImmer中的数据是数组,在set修改时数组方法都可以用,而useState初始的数组中,不能用那些会修改原数组的方法
useEffect使用副作用
- 语法:useEffect(effect,deps)
- effect副作用函数,该函数在组件挂载完成时默认触发一次,如果deps声明的数据发生变化之后,该函数会再次触发
- deps依赖的数据数组,当依赖项数据改变时effect会再次触发,可选参数,当不写时,任意数据(state,props)改变都会触发effect,给空数组时,任何数据更新都都不会再触发effect
function App(){
const [num,setNum] = useState(1);
useEffect(()=>{console.log('我被触发了')},[num])
return (
<button onClick={()=>setNum((prevNum)=>prevNum+1)}>{num}</button>
)}
在点击按钮修改num时,console.log会被再次触发
- 清理effect
- 在effect函数返回一个函数时,这个返回的函数在后续deps改变触发effect时,会先执行上一次effect返回的函数再执行effect
- 使用场景:在effect中做了消息订阅,事件绑定,定时器等相关操作时,需要处理清理函数,
简单说:当它第一次执行的时候定义了定时器,而deps改变再次触发执行的时候,上一个定时器就需要清除
useLayoutEffect,功能与useEffect类似
在DOM改变后调用,useLayoutEffect的执行时机要早于useEffect,
在18版本之前,useEffect修改DOM样式时可能存在闪烁问题,因为他在绘制完屏幕才执行,这时可以用useLayoutEffect就不存在这个问题
useContext上下文对象,勾出某个Context对象的内容
- 语法:const value = useContext(context)
- context就是某个React.createContext()创建出来的对象
- value 返回值,返回该context对象提供的值
//引入
import { useContext, createContext } from 'react'
//创建context的对象
const MyContext = createContext()
function App(){
return (
//MyContext.Provider包裹下的后辈组件都能获取value值
<MyContext.Provider value={"张三"}>
<Hello></Hello>
</MyContext.Provider>
)
}
function Hello(){
//儿辈组件,通过useContext(MyContext)获取长辈组件传的value值
const value = useContext(MyContext)
return (
<div>
<h1>Hello-{value}</h1>
<Hello2 />
</div>
)}
function Hello2(){
//孙辈组件,通过useContext(MyContext)获取长辈组件传的value值
const value = useContext(MyContext)
return (
<div>
<h1>Hello2-{value}</h1>
</div>
)}
从这可以看出,他就像vue的依赖注入(provide与inject),父组件注入数据,在后代组件中都能直接通过useContext获取到
useRef创建一个Ref对象
- 语法:const myRef = useRef(initialValue)
- initialValue初始值
- myRef 返回值是个对象,myRef的值是initialValue初始值,如果在标签上标记了ref,则在挂载完成后,myRef是标记的元素节点
//引入
import { useRef, useEffect } from 'react'
function App(){
<Hello></Hello>
}
function Hello(){
const myRef = useRef('1')
console.log(myRef);//这里打印的是1
useEffect(()=>{console.log(myRef)})//这里打印的是h1的DOM节点,因为useEffect是在挂载完成时执行的,h1上给ref做了标记获取h1的DOM节点
return (
<div>
<h1 ref={myRef}>Hello-{value}</h1>
</div>
)}
Ref转发,forwardRef
- 函数组件不能通过字符串方式的ref标记,因为字符串方式标记的,后续需要this.ref来获取,而函数组件没有this
- 同时,ref通过useRef()标记一个函数组件也会报错,
- 需要通过React.forwardRef()做转发
- 语法:React.forwardRef()高阶组件,接收一个组件作为参数,返回一个新组件
- const NewComponent = React.forwordRef(WrappendComponent)
//引入
import { useRef, forwardRef } from 'react'
//3.这里子组件接收的第二个参数myHRef,就是App组件中标记的这个被包装了的组件,相当于把ref传过来给你用,你再传回来我要的东西
function Hello(props,myHRef){
return (
<div>
//4.将这个myHRef标记到组件中的标签,在App组件中的myRef就能获取到函数组件内的DOM
<h1 ref={myHRef}>Hello</h1>
</div>
)}
//1.调用forwardRef()对Hello组件做包装
const NewHello = forwardRef(Hello)
//2.App组件使用包装后的组件,标记ref
function App(){
const myRef = useRef()
return(
<NewHello ref={myRef}></NewHello>
)
}
这里顺序比较绕,按序号步骤来看
useImperativeHandle处理函数组件对外暴露的内容
- 需要结合forwardRef()使用
- 语法:useImperativeHandle(ref,()=>{return 要暴露的内容})
//引入
import { useRef, forwardRef, useImperativeHandle } from 'react'
//这里子组件接收的第二个参数myHRef,就是App组件中标记的这个被包装了的组件,相当于把ref传过来给你用,你再传回来我要的东西
const Hello = forwardRef(function(props,ref){
useImperativeHandle(ref,()=>{return '123'})
return (
<div>
<h1>Hello</h1>
</div>
)}
)
//App组件使用包装后的组件,标记ref
function App(){
const myRef = useRef()
return(
<Hello ref={myRef}></Hello>
)
}
- 通过这个方式,myRef对象中有个current拿到的就是子组件useImperativeHandle中第二个参数暴露出来的东西
- 可以通过这个方式,暴露一些方法让外部的组件能够操作该组件
React.memo()
- 一个高阶组件,用于函数组件,让函数组件有缓存
- 类似于类组件中使用PureComponent
React.useMemo(计算函数,deps)让函数组件有缓存,可以当vue的计算属性使用
- 结合React.memo使用
- 语法:const data = useMemo(()=>{计算函数,相当于vue计算属性中的get},[依赖数组])
- 计算函数,必须有返回值否则useMemo得到的结果为undefined,该函数默认触发一次,后续由依赖数组变化时触发
- deps依赖数组,计算函数中用到的数据,都需要设置为deps的项
- data就是useMemo的返回值,有缓存
React.useCallback()缓存函数
- 结合React.memo使用
- 类似于useMemo,但是useCallback缓存的是函数,useMemo缓存的是值
- 语法:const fn = useCallback(函数,deps)
- 函数,需要被缓存的函数
- deps,依赖数组,当依赖数组发生变化,缓存会重新执行
- fn,缓存的函数
useReducer()
- 语法:const [state,dispatch] = useReducer(reducer,initState)
- initState 初始的state数据
- reducer 一个函数,接收两个参数(state,action)
该函数在调用dispatch时触发
state 表示上一次的state数据
action 表示动作,也就是dispatch调用时传递过来的action
该函数需要返回一个全新的state数据,返回的state数据,会给到state状态数据 - state 状态数据
默认初始由initState决定
后续由reducer的返回值决定 - dispatch 派发动作的函数
dispatch(action)
action 是一个动作,一个包含有type属性的一个对象,type值是动作函数名
useDebugValue()
- 语法:useDebugValue(value,format?)
- 作用:
- 用于自定义hook,在devtools中有个自定义的提示,这个提示值就是value值
- format可选的格式化函数,该函数把value作为参数,它的返回值成为devtools的提示值
- 自定义hook就是定义一个以use开头的函数,这个函数中也可以使用其他自定义hook或内置hook
useId()用于生成唯一id
useDeferredValue()获取一个延迟的值
- 性能优化,降低某次状态更新的优先级
- 语法:const deferredValue = useDeferredValue(value)
- value 要基于什么数据,一般是一个state或prop数据
- deferredValue 基于value的一个延迟值
- 初始化阶段,deferredValue的值等于value
- 更新阶段,如果value有更新,deferredValue会引起组件两次更新
1.第一次的值是value上一次的值
2.第二次的值是value最新的值
useTransition()使用过度
- 性能优化,降低某次状态更新的优先级
- 语法:const [isPending,startTransition] = useTransition()
- isPending 表示当前是否有状态更新处理过度状态,一个布尔值
- startTransition 一个函数,接收一个callback做为参数,callback内部执行的状态
useSyncExternalStore()订阅外部数据的变化
- 语法:const state = useSyncExternalStore(subscrie,getSnapshot)
-
- subscribe - 订阅函数,内部需要返回取消订阅的函数
初始化阶段,会触发 subscribe 函数一次,且会携带一个 listener(订阅者) 过去。
在该函数中,需要将 listener 做保存,以供后续通知他们我的数据有变化,
- subscribe - 订阅函数,内部需要返回取消订阅的函数
-
- getSnapshot - 获取快照的一个函数,该函数内部需要返回一份数据,返回的数据就是 state
当外部源通知订阅者有数据变化之后,订阅者会重新调用 getSnapshot 从而得到外部数据源中最新的数据
- getSnapshot - 获取快照的一个函数,该函数内部需要返回一份数据,返回的数据就是 state
-