聊聊React日常研发最常用的15个Hooks

前有科技后进阶 2024-03-26 07:18:02

大家好,很高兴又见面了,我是"高级前端‬进阶‬",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。

在 React Hooks (React < 16.8) 之前,开发人员需要编写类组件才能利用某些 React 功能。 但现在,React Hooks 提供了一种更符合人体工程学的方式来构建组件,因为可以使用有状态逻辑而无需更改组件层次结构。

1. useState

useState 是最重要也是最常用的 Hooks。

useState Hook 的目的是处理反应性数据,应用程序中发生变化的任何数据都称为状态,当数据发生变化时,React 会重新渲染 UI。

const [count, setCount] = React.useState(0);2. useEffect

useEffect Hook 允许开发者在函数组件中执行副作用,包括:

从 API 获取数据更新 DOM订阅事件等// 组件挂载和state数据变化都会调用,很显然第二个参数相当于完整的stateReact.useEffect(() => { alert('Hey, Nads here!');});// 组件首次挂载调用useEffect(() => { fetch('https://api.example.com/data') .then((response) => response.json()) .then((data) => setData(data));}, []);// 当state中的count变化的时候调用React.useEffect(() => { fetch('nads').then(() => setLoaded(true));}, [count]);// 当组件被销毁或者组件从界面移除之前调用React.useEffect(() => { alert('Hey, Nads here'); return () => alert('Goodbye Component');});3. useContext

该 Hooks 允许开发者使用 React 的 Context API,它本身是一种机制,允许在组件树中共享数据而无需通过 props,从而消除 prop-drilling:

const ans = { right: '✅', wrong: '❌',};const AnsContext = createContext(ans);// 创建一个contextfunction Exam(props) { return ( // 任何内部组件都可以使用context的值 <AnsContext.Provider value={ans.right}> <RightAns /> </AnsContext.Provider> );}function RightAns() { // 从最近的父级Provider消费值 const ans = React.useContext(AnsContext); return <p>{ans}</p>; // 以前需要包裹在AnsContext.Consumer中,现在已经不需要了}4. useRef

该 Hooks 允许创建一个可变对象(Mutable Object)。 当值经常变化时可以使用,就像 useState Hook 一样,但不同的是,当值变化时不会触发重新渲染。

其常见用例是从 DOM 中获取 HTML 元素。

function App() { const myBtn = React.useRef(null); const handleBtn = () => myBtn.current.click(); return <button ref={myBtn} onChange={handleBtn}></button>;}5. useReducer

功能与 setState 非常相似,是使用 Redux 模式管理状态的不同方式。

useReducer 不直接更新状态,而是 dispatch 动作,将其发送到 reducer 函数,然后该函数计算出下一个状态。

function reducer(state, dispatch) { switch (action.type) { case 'increment': return state + 1; case 'decrement': return state - 1; default: throw new Error(); }}function useReducer() { // state is the state we want to show in the UI. const [state, dispatch] = React.useReducer(reducer, 0); return ( <> Count : {state} <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <button onClick={() => dispatch({ type: 'increment' })}>+</button> </> );}6. useMemo

useMemo Hook 允许开发者存储一个值,以便仅在其依赖项发生变化时才重新计算。这可以通过防止不必要的重新计算来帮助提高性能,推荐在需要进行昂贵计算时使用。

function useMemo() { const [count, setCount] = React.useState(60); const expensiveCount = useMemo(() => { return count ** 2; }, [count]); // 当count的值变化时候重新计算expensiveCount的值}7. useCallback

useCallback Hook 允许记忆一个函数,以便仅在其依赖项发生变化时才重新创建,从而可以通过防止不必要的重新渲染来帮助提高性能。

以下是如何使用 useCallback 来记忆函数的示例:

import React, { useState, useCallback } from 'react';function SearchBar({ onSearch }) { const [query, setQuery] = useState(''); // onSearch的prop变化时候更新 const handleQueryChange = useCallback( (event) => { setQuery(event.target.value); onSearch(event.target.value); }, [onSearch] ); return <input type="text" value={query} onChange={handleQueryChange} />;}

在此示例中,定义了一个带有 onSearch prop 函数的 SearchBar 组件。使用 useCallback Hooks 来记忆 handleQueryChange 函数,以便仅在 onSearch 函数更改时才重新创建它。

8. useImperativeHandle

useImperativeHandle Hook 允许开发者自定义在使用 ref 时向父组件公开的实例值。 当需要向父组件提供某个接口,但又不想公开子组件的所有内部实现细节时,这可能很有用。

以下是如何使用 useImperativeHandle 的示例:

import React, { useRef, useImperativeHandle } from 'react';const Input = React.forwardRef((props, ref) => { const inputRef = useRef(); // 允许开发者自定义在使用 ref 时向父组件公开的实例值 useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, value: inputRef.current.value, })); return <input type="text" ref={inputRef} placeholder={props.placeholder} />;});function App() { const inputRef = useRef(); const handleClick = () => { inputRef.current.focus(); }; return ( <div> <Input ref={inputRef} placeholder="Type here" /> {/*Input的ref传递下去*/} <button onClick={handleClick}>Focus input</button> </div> );}

此示例定义了一个自定义 Input 组件,该组件在使用 ref 时使用 useImperativeHandle 向父组件公开焦点方法和 value 属性。 useImperativeHandle Hook 采用两个参数:ref 对象和回调函数,该函数返回一个对象,该对象具有应公开给父组件的属性和方法。

在 App 组件中使用 Input 组件并向其传递一个 ref 对象,还定义了一个 handleClick 函数,当单击按钮时,该函数调用 inputRef 对象的 focus 方法。

9. useLayoutEffect

工作原理与 useEffect Hook 相同,但有一点不同,回调将在渲染组件之后但在实际更新绘制到屏幕之前运行。 即,阻止视觉更新,直到回调完成。

function useLayoutEffectDemo() { const myBtn = React.useRef(null); React.useLayoutEffect(() => { const rect = myBtn.current.getBoundingClientRect(); // scroll position before the dom is visually updated console.log(rect.height); });}10. useDebugValue

useDebugValue 是一个 Hook,允许开发者在 React DevTools 中显示自定义钩子的自定义调试信息。这对于调试 Hook 和了解幕后发生的事情很有用。

假设有 n 个使用相同逻辑的组件,那么可以单独定义自己的函数并且可以在其他组件中使用,但这里的关键是可以调试东西:

import { useState, useEffect, useDebugValue } from 'react';function useFetch(url) { const [data, setData] = useState(null); useEffect(() => { fetch(url) .then((response) => response.json()) .then((data) => setData(data)); }, [url]); useDebugValue(data ? `Data loaded: ${data.length} items` : 'Loading...'); return data;}

在上面示例中定义了一个名为 useFetch 的自定义 Hook,用于从 URL 获取数据并返回。 使用 useDebugValue Hook 在 React DevTools 中显示自定义调试消息。 如果数据已加载,会显示一条消息,其中包含数据中的项目数。 如果数据仍在加载会显示一条消息“正在加载...”。

当在组件中使用 useFetch Hook 时,自定义调试消息将显示在 React DevTools 中。

11. useEffectEvent

上面说过,React 的 useEffect hook 用于在函数组件中执行副作用操作。而 useEffectEvent 是 React-use 库中提供的一个额外的 hook,是基于 useEffect 封装的一个事件监听器。

useEffectEvent 的主要场景是在函数组件中监听事件,这些事件包括:

Window 事件:resize、scroll、load、unload、beforeunload、popstateDocument 事件:mousemove、mousedown、mouseup、keypress、keydown、keyup、wheelXMLHttpRequest 事件:loadstart、load、progress、abort、error、timeout、loadendCustom 事件:自定义事件

useEffectEvent 与 useEffect 的区别在于,前者会自动移除事件监听器,而 useEffect 需要在返回函数中手动清除副作用。此外,useEffectEvent 还提供了一个回调函数,可以在事件发生时执行。

下面是一个 useEffectEvent 的使用示例:

import { useEffect, useState } from 'react';import { useEffectEvent } from 'react-use';function App() { const [scrollY, setScrollY] = useState(window.scrollY); const handleScrollY = () => { setScrollY(window.scrollY); }; useEffect(() => { window.addEventListener('scroll', handleScrollY); // 需要手动移除 return () => { window.removeEventListener('scroll', handleScrollY); }; }, []); useEffectEvent('scroll', handleScrollY); return <div></div>;}export default App;12. useInsertionEffect

调用 useInsertionEffect 在任何可能需要读取布局的效果触发之前插入样式:

// CSS-in-JS 库内部逻辑let isInserted = new Set();function useCSS(rule) { useInsertionEffect(() => { // 如前所述,不建议运行时注入 <style> 标签 // 如果必须这么做,建议在 useInsertionEffect中。 if (!isInserted.has(rule)) { isInserted.add(rule); document.head.appendChild(getStyleForRule(rule)); } }); return rule;}function Button() { constName = useCSS('...'); return <divName={className} />;}

方法接受两个参数:

setup: 具有副作用逻辑的函数。设置函数也可以选择返回一个清理函数,当组件从 DOM 中删除时,React 会自动运行。可选的 dependencies: setup 代码中引用的所有反应值的列表。

需要注意的是:

副作用仅在客户端上运行,不会在服务器渲染期间运行无法从 useInsertionEffect 内部更新状态useInsertionEffect 运行时无法获取 refuseInsertionEffect 可能在 DOM 更新之前或之后运行,不应该依赖于在任何特定时间更新 DOM。与其他 Effect 不同的是,其他类型的 Effect 会为每个 Effect 触发清理,然后为每个 Effect 进行设置,useInsertionEffect 在一个组件上只会设置、清理一次。13.useMemoCache

目前 useMemoCache(size: number): Array Hooks 仅用作实验性构建时自动记忆功能的编译目标(compilation target)。

这个 Hooks 手动调用是不安全的,虽然 API 强大但违反 React 规则,用户有责任确保使用安全, 而构建时工具保证转换后的代码安全地使用 API。

备注:useMemoCache 就是 React 内部为 React Forget 提供缓存支持的 hook。

React Forget :目标是确保 React 应用在默认情况下具有适量的响应性,即应用仅在状态值发生有意义的变化时才重新渲染。

从实现的角度来看,这意味着自动记忆,但 React 团队认为响应式框架是理解 React 和 Forget 的更好方式。React 目前会在对象标识更改时重新渲染。有了 Forget,React 会在语义值发生变化时才重新渲染—,从而不会产生深度比较的运行时成本。

14/15. useTransition/useDeferredValue

参考我写的另外一篇文章《React 为什么会推出 useTransition() 和 useDeferredValue() 两个 Hook?》

参考资料

https://dev.to/abhisheknaiidu/10-react-hooks-explained-3ino

https://medium.com/@AbidKazmi/all-react-hooks-in-one-short-4b0ed4b5a6e4

https://zhuanlan.zhihu.com/p/634140304

https://react.dev/learn/separating-events-from-effects

https://jser.dev/react/2023/03/18/useeffectevent/

https://qianduan.shop/blogs/detail/125#google_vignette

https://react.dev/reference/react/useInsertionEffect

https://github.com/facebook/react/pull/25123

https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023

https://www.protonshub.com/blogs/react-hooks-deep-dive-advanced-usage-and-patterns

0 阅读:2

前有科技后进阶

简介:感谢大家的关注