下面,我将我最近学习到的React Hooks相关的知识整理为博客,给大家一起分享!
Hooks是React 16.8中的新增功能。它们使开发者在不编写 class 的情况下使用 state 以及其他的 React 特性。
早期的React 只支持纯函数组件,如
//纯函数组件
function Welcome(props) { return <h1>Hello, {props.name}</h1>;}
这种纯函数的写法有着重大限制,即不能包含状态,也不支持生命周期方法,因此,无法取代类。
而使用类进行开发又存在组件间状态逻辑难以复用、复杂组件难以理解等问题。
为了解决上述问题,React官方设计了Hooks,以实现加强函数组件的目的,进而让开发者可以在完全不使用“类”的状态下,就可以写出一个全功能的组件。
引入Hooks后的组件主体仍为返回组件的HTML代码,若开发者需要外部功能或其他操作(副作用),就可以用钩子函数把外部代码“钩”进来。
显然,React Hooks 指的即为那些钩子函数。
由于副作用非常多,所以React Hooks有很多种,下面我将主要介绍几种常用的Hooks.
1. useState
// useState的函数签名
const [state, setState] =useState(initalState);
l 基本用法:
接受状态的初始值(initialState)为参数,返回一个数组。数组的第一个成员为状态的当前值(state),数组的第二个成员为更当前状态的函数(setState)。
l 注意:
(1)initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用。
(2)在初始渲染期间,返回的状态(state) 与传入的第一个参数 (initialState) 值相同。
(3)在后续的重新渲染中,useState 将接收一个新的 state 值并将组件的一次重新渲染加入队列,即setState(newState)
2. useEffect
//useEffect的函数签名
useEffect(didUpdate);
接受状态的初始值(initialState)为参数,返回一个数组。数组的第一个成员为状态的当前值(state),数组的第二个成员为更当前状态的函数(setState)。
l 基本用法
把更新函数和依赖项数组作为参数传入 useEffect,默认情况下,useEffect将在每轮渲染结束后执行,开发者也可以选择让它只在依赖项数组发生变化时才执行。
l 注意:
(1) useEffect 执行顺序为:组件更新挂载完成 —> 浏览器dom 绘制完成 --> 执行useEffect回调 。
(2) 如果只想执行一次effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。
(3) 如果想清理副作用,可以在useEffect() 中返回一个函数,在组件卸载时,执行该函数,实现清理副效应的目的。如果不需要清理副效应,useEffect()就不用返回任何值。
3. useContext
//useContext的函数签名
const value = useContext(MyContext);
l 基本用法
Context提供了一种通过组件树传递数据的方法,而不必在每个级别手动传递道具。
useContext接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。其当前的context值由上层组件中距离当前组件最近的
useContext的具体使用步骤如下:
(1)创建Context
(2)使用MyContext.Provider提供value (3)使用useContext(MyContext)获得数值value
l 注意:
(1)Context需要在多个文件中传递数据,所以要在定义Context的地方加入export,在需要使用数据地方import进来,注意import的时候一定要加上花括号{MyContext}(原因请关注默认export和命名export的区别)。,
(2)不要在一个文件中provider提供完数据后,马上useContext消费数据,可能会取不到值。
(3)useContext的参数必须是上下文对象本身: 正确的: useContext(MyContext)
不正确: useContext(MyContext.Consumer)
不正确: useContext(MyContext.Provider)
4. useReducer
//useReducer的函数签名
const [state, dispatch] = useReducer(reducer, initialArg, init);
l 基本用法
useReducer接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。
l 注意:
(1) 开发者可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)。
(2) dispatch 的触发会触发组件的更新
(3) 如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。
5. useCallback
//useCallback的函数签名
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b],);
l 基本用法
将内联回调函数及依赖项数组作为参数传入useCallback,它将返回该回调函数的memoized版本,该回调函数仅在某个依赖项改变时才会更新。
6. useMemo
//useMemo的函数签名
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
l 基本用法
将“创建”函数和依赖项数组作为参数传入useMemo,它将返回一个返回一个memoized 值。useMemo仅会在某个依赖项改变时才重新计算 memoized 值。
l 注意:
(1) 传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作。
(2) 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
(3)useMemo 会「记住」一些值,同时在后续 render 时,将依赖数组中的值取出来和上一次记录的值进行比较,如果不相等才会重新执行回调函数,否则直接返回「记住」的值。
7. useRef
//useRef的函数签名
const refContainer = useRef(initialValue);
l 基本用法
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变
l 注意:
(1) 传当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
(2) 本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。 它可以很方便的保存任何可变值,其类似于在 class 中使用实例字段的方式。
1. useState和useReduce
setState 和 useReducer 的功能很类似,都是状态管理。理论上他们两个的功能是用另一个可以代替的。为什么 React 要提供这样两个功能有重叠的 API 呢?
事实上,useReducer是useState的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。
并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为开发者可以向自组件传递dispatch 而不是回调函数。
那么,开发者应该使用useState还是useReducer?:
(1) 当 state 只是用来描述一个独立的元素时,应优先使用 useState
(2)当 state 中的一个元素依赖于另一个 state 的值来进行更新时,应优先使用 useReducer
当然,上述规则不是必须,只做参考,具体情况可根据开发情景做抉择。
2. useState和useRef
useState 和 useRef 都可以在component 生命周期内保存数据,且在re-render都仍然可以持续保存,那么他们二者有什么区别呢?
useState更新数据源的函数执行必定会带来整个组件重新执行到渲染,如果在函数组件内部声明变量,则下一次更新也会重置。
而useRef在更新数据源时不会重新触发函数的更新,因此,组件也不会重新渲染。也就是说,当数据发生变化后,不会呈现在页面中。
因此,开发者在选用 useState 和 useRef时,可参考: 是否牵涉页面显示? useState : useRef
3. useEddect和useLayoutEffect
useLayoutEffect和 useEffect 的结构相同,都会将更新函数和依赖项数组作为参数传入。那么他们二者的区别是什么呢?
useEffect在浏览器完成布局与绘制之后,会延迟调用,因此适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。 然而,并非所有 effect 都可以被延迟执行。例如,在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致。
为此,React 为此提供了一个额外的 useLayoutEffect Hook来处理这类 effect。
useLayoutEffect会在所有的 DOM 变更之后同步调用effect,常用来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。 因此,可以看出useLayoutEffect 和 useEffect 区别只是调用时机不同。
useEffect 执行顺序为:组件更新挂载完成 --> 浏览器dom 绘制完成 -->执行useEffect回调 。
useLayoutEffect 执行顺序为: 组件更新挂载完成 --> 执行useLayoutEffect回调–> 浏览器dom 绘制完成
4. useMemo和useCallback
useMemo 和 useCallback 都是 React 提供来做性能优化的。 且useMemo和useCallback接收的参数都是一样,都是在其依赖项发生变化后才执行、重新渲染,都是返回缓存的值。那么二者的区别是什么呢?
首先,我们需要清楚,父组件将一个方法传递给子组件,若父组件的其他状态发生变化时,子组件也会跟着渲染多次,会造成性能浪费。
useMemo 是将父组件传递给子组件的值缓存起来,只有当 useMemo中的第二个参数状态变化时,子组件才重新渲染,返回的是函数运行的结果。
而useCallback(fn, deps) 相当于 useMemo(()=> fn, deps)。
显然,usecallback是将父组件传给子组件的方法给缓存下来,只有当 usecallback中的第二个参数状态变化时,子组件才重新渲染,返回的是函数。