hooks 功能分类 | 具体 hooks | 具体功能 | React v18新特性 | 跨端支持 |
---|---|---|---|---|
数据更新驱动 | useState | 定义要在页面中渲染的数据 | ❌ | ✔ |
useReducer | 定义要在页面中渲染的数据,且这个数据有多种处理逻辑 | ❌ | ✔ | |
useSyncExternalStore | concurrent 模式下,订阅外部 store 的行为,触发更新 | ✔ | ❌ | |
useTransition | concurrent 模式下,在不阻塞 UI 的情况下来更新状态 | ✔ | ❌ | |
useDeferredValue | concurrent 模式下,延迟更新 UI 的某些部分 | ✔ | ❌ | |
执行副作用 | useEffect | 异步状态下,视图更新后,执行副作用 | ❌ | ✔ |
useLayoutEffect | 同步状态下,视图更新前,执行副作用 | ❌ | ✔ | |
useInsertionEffect | 用于处理 CSS-in-JS 缺陷问题 | ✔ | ❌ | |
状态获取与传递 | useContext | 接收祖先组件的 context 传递的信息 | ❌ | ✔ |
useRef | 存储一个不需要渲染的值 | ❌ | ✔ | |
useImperativeHandle | 配合 forwardRef 将子组件的 ref 传递给父组件 | ❌ | ✔ | |
状态派生与保存 | useMemo | 缓存结果 | ❌ | ✔ |
useCallback | 缓存函数 | ❌ | ✔ | |
工具 hooks | useId | 生成唯一的 ID | ✔ | ❌ |
useDebugValue | 在 React 开发者工具中为自定义 Hook 添加标签 | ❌ | ❌ |
useState 允许向组件添加一个 状态变量。
/**
* @param { any } initValue:初始值
* @return { array } arr:状态信息
state { any } 状态名
setState { function } 修改状态的函数
*/
const [state, setState] = useState(initValue)
// 第一种方式:传入值
setState(newValue);
// 第二种方式:传入函数
setState(preValue => newValue);
示例:
const [number, setNumber] = useState(0)
// 第一种方式:传入值
setNumber(number + 1)
// 第二种方式:传入函数
setNumber(number => number + 1)
注意事项:
- 在函数组件一次执行上下文中,state 的值是固定不变的
- 如果两次 setState 传入相同的 state 值,那么组件就不会更新
- 当触发 setState 在当前执行上下文中获取不到最新的 state,只有在下一次组件 render 中才能获取到
useReducer 能够在无状态组件中运行的类似 redux 的功能 api 。
当对一个状态有多种处理逻辑时建议使用 useReducer。
/**
* @param { function } reducer:处理函数
* @param { any } initValue:初始值
* @param { function } compareInitValueFn:计算初始值的函数,如果存在则初始值为 compareInitValueFn(initValue)
* @return { array } arr:状态信息
state { any } 状态名
dispatchState { function } 派发状态的函数
*/
const [state, dispatchState] = useReducer(stateReducer, initValue, compareInitValueFn?)
示例:
const numberReducer = (state, action) => {
switch(action.type) {
case 'add':
return state + 1;
case 'reduce':
return state - 1;
}
}
const [number, dispatchNumber] = useReducer(numberReducer, 0);
dispatchNumber({
type: 'add'
})
useSyncExternalStore 可以订阅外部 store。
/**
* @param { function } subscribe:订阅函数
* @param { function } getSnapshot:返回组件需要的 store 中的数据快照 带有记忆功能的选择器
* @param { function } getServerSnapshot:用于 hydration 模式下的 getSnapshot
* @return { any } snapshot:该 store 的当前快照
*/
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
示例:
import { combineReducers , createStore } from 'redux'
/* number Reducer */
function numberReducer(state = 1, action) {
switch(action.type) {
case 'ADD':
return state + 1
case 'DEL':
return state - 1
default:
return state
}
}
/* 注册reducer */
const rootReducer = combineReducers({ number: numberReducer })
/* 创建 store */
const store = createStore(rootReducer, { number: 1})
function Index(){
/* 订阅外部数据源 */
const state = useSyncExternalStore(store.subscribe, () => store.getState().number)
return (
)
}
注意:
getSnapshot 返回的 store 快照必须是不可变
如果底层 store 有可变数据,要在数据改变时返回一个新的不可变快照;否则,返回上次缓存的快照。
如果在重新渲染时传入一个不同的 subscribe 函数,React 会用新传入的 subscribe 函数重新订阅该 store
可以通过在组件外声明 subscribe 来避免。
useTransition 可以在不阻塞 UI 的情况下来更新状态。
/**
* @return { array } transitionInfo:转换状态信息
isPending { boolean } 是否存在待处理的转换
startTransition { function } 将状态由更新状态标记为转换状态
@param { function } scope:作用域函数
*/
const [isPending, startTransition] = useTransition()
示例:
export default function Index(){
const [number, setNumber] = React.useState(0) // 需要立即响应的任务,立即更新任务
const [count, setCount] = useState(0) // 不需要立即响应的任务,过渡任务
const [isPending, startTransition] = useTransition()
const btnClick = () => {
setNumber(number + 1) // 立即更新
startTransition(() => { // startTransition 里面的任务优先级低
setCount(count + 1);
})
}
return (
)
}
注意:
只有在可以访问该状态的
set
函数时,才能将更新包装为转换状态如果想响应某个 prop 或自定义 Hook 值启动转换,请尝试使用 useDeferredValue
传递给 startTransition 的函数必须是同步的。
useDeferredValue 可以延迟更新 UI 的某些部分。
/**
* @param { any } value:延迟更新的值
* @return { any } deferredValue:延迟更新的值
*/
const deferredValue = useDeferredValue(value)
示例:
export default function Index(){
const [number, setNumber] = useState(1) // 需要立即响应的任务,立即更新任务
const deferNumber = useDeferredValue(number) // 把状态延时更新,类似于过渡任务
const btnClick = () => {
setNumber(number + 1) // 立即更新
}
return (
)
}
- 在组件的初始渲染期间,返回的延迟值将与提供的值相同
- 但是在组件更新时,React 将会先尝试使用旧值进行重新渲染(因此将返回旧值)
- 然后再在后台使用新值进行另一个重新渲染(这时将返回更新后的值)
注意:应该向 useDeferredValue 传递原始值或在渲染之外创建的对象。
如果在渲染期间创建了一个新对象,并立即将其传递给 useDeferredValue,那么每次渲染时这个对象都会不同,这将导致后台不必要的重新渲染。
useEffect 用于异步监听组件的 state 属性,在浏览器绘制后执行。
/**
* @param { function } setup:回调函数,初始化执行一次,当监听的 state 改变时执行,返回一个当组件被销毁时执行的函数
* @param { array } dependencies:要监听的 state,默认为所有 state,空数组表示不监听任何 state
*/
useEffect(setup, dependencies?)
示例:
useEffect(() => {
let timer = setInterval(() => {
console.log(1)
}, 1000)
return () => {
timer = null
}
}, [])
可以把 useEffect 看作是以下三个生命周期的组合:
- componentDidMount():组件挂载完成执行 callback
- componentDidUpdate():监听的 state 变化执行 callback
- componentWillUnmount():组件将要销毁执行 callback 的返回函数
useLayoutEffect 是 useEffect 的同步版本,并且是在浏览器绘制前执行,主要用于操作 DOM。
注意:useLayoutEffect callback 中代码执行会阻塞浏览器绘制
/**
* @param { function } setup:回调函数,初始化执行一次,当监听的 state 改变时执行,返回一个当组件被销毁时执行的函数
* @param { array } dependencies:要监听的 state,默认为所有 state,空数组表示不监听任何 state
*/
useLayoutEffect(setup, dependencies?)
useInsertionEffect 可以在布局副作用触发之前将元素插入到 DOM 中。
注意:useInsertionEffect 是为 CSS-in-JS 库的作者特意打造的。除非正在使用 CSS-in-JS 库并且需要注入样式,否则应该使用 useEffect 或者 useLayoutEffect。
/**
* @param { function } setup:回调函数,初始化执行一次,当监听的 state 改变时执行,返回一个当组件被销毁时执行的函数
* @param { array } dependencies:要监听的 state,默认为所有 state,空数组表示不监听任何 state
*/
useInsertionEffect(setup, dependencies?)
useContext 用于接收祖先组件的 context 传递的信息。
/**
* @param { context } context:context 容器
* @return { any } value:祖先组件传递的 context
*/
const value = useContext(context)
示例:
// 1、创建 context 容器
const NumberContext = createContext(null);
// 2、祖先组件定义 Provider
function GrandFather() {
return (
);
}
// 3、父组件中使用子组件
function Father() {
return (
)
}
// 4、子组件中通过 useContext 接收祖先组件传递的数据
function Son() {
const number = useContext(NumberContext);
}
useContext()
总是在调用它的组件 上面 寻找最近的 provider。它向上搜索,不考虑 调用useContext()
的组件中的 provider。
useRef 可以存储一个不需要渲染的值。
与 state 的区别:ref 的改变不会渲染,state 会
与普通对象的区别:ref 的值不会重置,普通对象会
/**
* @param { any } initValue:初始值
* @return { ref } ref:只有 current 属性的 ref 对象
*/
const ref = useRef(initValue)
示例:
function Demo() {
const divRef = useRef(null)
return (
ref节点
)
}
不要在 渲染期间 写入或者读取
ref.current
。可以在 事件处理程序或者 effects 中读取和写入 ref。
useImperativeHandle 配合 forwardRef 将子组件的 ref 传递给父组件。
/**
* @param { ref } ref:forWardRef 渲染函数中获得的第二个参数
* @param { function } createHandle:处理函数,返回值作为暴露给父组件的 ref 对象
* @param { array } dependencies:依赖项
*/
useImperativeHandle(ref, createHandle, dependencies?)
示例:
// 子组件
const Son = forwardRef((props, ref) => {
const divRef = useRef(null)
useImperativeHandle(ref, () => {
return {
sendData: () => {
console.log(divRef.current)
}
}
}, [])
return 子组件
})
// 父组件
function Parent() {
const sonRef = useRef(null)
return
}
useMemo 在每次重新渲染的时候能够缓存计算的结果。
/**
* @param { function } calculateValue:要缓存计算值的函数,返回值作为缓存值
* @param { array } dependencies:依赖项数组
* @return { any } cachedValue:缓存值(calculateValue 返回的值)
*/
const cachedValue = useMemo(calculateValue, dependencies)
- cachedValue 初次为不带参数调用 calculateValue 的返回值
- 后续如果依赖项没有变,就返回上次缓存的值;否则将再次调用 calculateValue,并返回最新结果
注意:应该仅仅把 useMemo 作为性能优化的手段
useCallback 允许在多次渲染中缓存函数。
/**
* @param { function } fn:想要缓存的函数
* @param { array } dependencies:依赖项数组
* @return { function } cachedFn:缓存的函数
*/
const cachedFn = useCallback(fn, dependencies)
- 在初次渲染时,useCallback 返回你已经传入的 fn 函数
- 在之后的渲染中, 如果依赖没有改变,useCallback 返回上一次渲染中缓存的 fn 函数;否则返回这一次渲染传入的 fn。
注意:
- 不应在循环或者条件语句中调用 useCallback
- 应该仅仅把 useMemo 作为性能优化的手段
useId 可以生成传递给无障碍属性的唯一 ID。
/**
* @return { string } id:唯一的字符串 ID
*/
const id = useId()
注意:不要使用 useId 来生成列表中的 key。
useDebugValue 可以在 React 开发工具 中为自定义 Hook 添加标签。
/**
* @param { any } value:在 React 开发工具中显示的值
* @param { function } format:如果传入,则值为将 value 作为参数调用 format 返回的值;否则值为 value
*/
useDebugValue(value, format?)