简介
hook钩子可以极大加强开发效率,适用于react函数式组件,函数式组件没有生命周期于是用钩子函数来进行代替,钩子的功能十分强大,例如可以实现双向数据绑定等等的一系列操作,下面是我整理的钩子学习记录。参考官方文档。
1、useCallback
useCallback
是一个允许你在多次渲染中缓存函数的 React Hook。
const cachedFn = useCallback(fn, dependencies)
参数
-
fn
:想要缓存的函数。此函数可以接受任何参数并且返回任何值。 -
dependencies
:有关是否更新fn
的所有响应式值的一个列表。响应式值包括 props、state,和所有在你组件内部直接声明的变量和函数。
返回值
在初次渲染时,useCallback
返回你已经传入的 fn
函数
在之后的渲染中, 如果依赖没有改变,useCallback
返回上一次渲染中缓存的 fn
函数;否则返回这一次渲染传入的 fn
。
用法
跳过组件的重新渲染,处理数据获取不变时的重复获取后进行渲染,处理出现闪屏等影响用户体验的问题
示例
import { useCallback, useState } from 'react';
const foo = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Clicked');
}, [count]);
function Child({ handleClick }:any) {
console.log('Child component rendered');
return <button onClick={handleClick}>Click Me</button>;
}
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Child handleClick={handleClick} />
</div>
);
}
export default foo;
结果
注释
在上面的例子中,foo
是一个函数组件,它有一个状态count
和一个处理点击事件的函数handleClick
。handleClick
中的console.log
语句只是一个示例,表示在真实场景中可能有更加复杂的操作。
Child
是foo
的子组件,它接收一个handleClick
函数作为props并将其绑定到一个按钮的点击事件上。
当我们点击Increment
按钮时,foo
的状态count
会递增,导致它重新渲染。但是由于handleClick
是通过useCallback
缓存的,它的函数实例在不变的情况下被重复使用,不会导致ChildComponent
重新渲染。
如果我们没有使用useCallback
,而是直接将一个内联函数传递给Child
,那么每次foo
重新渲染时,都会创建一个新的函数实例,导致Child
重新渲染。
使用useCallback
可以避免这种不必要的重新渲染,提高应用的性能。
2、useContext
useContext
是一个 React Hook,可以让你读取和订阅组件中的 context。
const value = useContext(SomeContext)
参数
SomeContext
:先前用 createContext 创建的 context。context 本身不包含信息,它只代表你可以提供或从组件中读取的信息类型。
返回值
useContext
为调用组件返回 context 的值。它被确定为传递给树中调用组件上方最近的SomeContext.Provider
的 value
。如果没有这样的 provider,那么返回值将会是为创建该 context 传递给 createContext 的 defaultValue
。返回的值始终是最新的。如果 context 发生变化,React 会自动重新渲染读取 context 的组件。
用法
向组件树深层传递数据,通过订阅接收方式,简化了多个父子间传值的繁琐性
示例
import { createContext,useContext } from "react";
// 创建一个上下文对象
const MyContext = createContext('');
// 在上下文对象中提供默认值
const MyProvider = ({ children }:any) => {
const value = "Hello from Context!";
return (
<MyContext.Provider value={value}>
{children}
</MyContext.Provider>
);
};
// 使用useContext访问上下文中的值
const MyComponent = () => {
const contextValue = useContext(MyContext);
return <div>{contextValue}</div>;
};
// 在根组件中使用提供者组件
const App = () => {
return (
<MyProvider>
<MyComponent />
</MyProvider>
);
};
export default App;
3、useDebugValue
useDebugValue
是一个 React Hook,可以让你在 React 开发工具 中为自定义 Hook 添加标签。由于作者不常用,因此不在赘述
4、useDeferredValue
useDeferredValue
是一个 React Hook,可以让你延迟更新 UI 的某些部分。
const deferredValue = useDeferredValue(value)
参数
value
:你想延迟的值,可以是任何类型。
返回值
在组件的初始渲染期间,返回的延迟值将与你提供的值相同。但是在组件更新时,React 将会先尝试使用旧值进行重新渲染(因此它将返回旧值),然后再在后台使用新值进行另一个重新渲染(这时它将返回更新后的值)。
用法
在新内容加载期间显示旧内容; 表明内容已过时 ;延迟渲染 UI 的某些部分 。
示例
结果
先输入SADASD,再退位到SAD,Data位置的数据出现延迟展示的效果
注释
在此示例中,我们使用useState
来管理输入框中的值和从后台加载的数据。当用户输入时,我们使用useEffect
设置一个定时器,模拟从后台加载数据。加载完成后,我们更新data
的状态。
但是,我们希望在数据加载完成之前不渲染<p>
元素。为了实现这一点,我们使用useDeferredValue
将data
的延迟版本赋给deferredData
。这意味着deferredData
将会在一段时间后更新为最新的值。
最后,我们将deferredData
渲染到屏幕上,而不是直接渲染data
。这样,即使数据加载完成之前,用户也不会看到旧的或不完整的数据。
通过使用useDeferredValue
,我们可以优化渲染和用户体验,确保数据加载完成后再进行渲染,而不是使用未加载或不完整的数据。
5、useEffect
useEffect
是一个 React Hook,它允许你 将组件与外部系统同步。基本上是react函数式组件必用hook,官方文档上很清楚,不再赘述。
6、useId
useId
是一个 React Hook,可以生成传递给无障碍属性的唯一 ID。
const id = useId()
返回值
useId
返回一个唯一的字符串 ID,与此特定组件中的 useId
调用相关联。
7、useImperativeHandle
useImperativeHandle
是 React 中的一个 Hook,它能让你自定义由 ref 暴露出来的句柄。
useImperativeHandle(ref, createHandle, dependencies?)
参数
-
ref
:该ref
是你从 forwardRef 渲染函数 中获得的第二个参数。 -
createHandle
:该函数无需参数,它返回你想要暴露的 ref 的句柄。该句柄可以包含任何类型。通常,你会返回一个包含你想暴露的方法的对象。 -
可选的
dependencies
:函数createHandle
代码中所用到的所有反应式的值的列表。反应式的值包含 props、状态和其他所有直接在你组件体内声明的变量和函数。倘若你的代码检查器已 为 React 配置好,它会验证每一个反应式的值是否被正确指定为依赖项。该列表的长度必须是一个常数项,并且必须按照[dep1, dep2, dep3]
的形式罗列各依赖项。React 会使用 Object.is 来比较每一个依赖项与其对应的之前值。如果一次重新渲染导致某些依赖项发生了改变,或你没有提供这个参数列表,你的函数createHandle
将会被重新执行,而新生成的句柄则会被分配给 ref。
返回值
useImperativeHandle
返回 undefined
。
用法
向父组件暴露一个自定义的 ref 句柄 ;暴露你自己的命令式方法
示例
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
// 子组件
const ChildComponent = forwardRef((props, ref) => {
const inputRef = useRef();
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({
focusInput: () => {
inputRef.current.focus();
},
getValue: () => {
return inputRef.current.value;
}
}));
return (
<input type="text" ref={inputRef} />
);
});
// 父组件
const ParentComponent = () => {
const childRef = useRef();
const handleButtonClick = () => {
childRef.current.focusInput();
const value = childRef.current.getValue();
alert(value);
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleButtonClick}>操作子组件</button>
</div>
);
};
export default ParentComponent;
结果
注释
在上面的例子中,我们定义了一个子组件ChildComponent
,其中包含一个输入框。我们使用useRef
来创建一个inputRef
引用,用于获取输入框的DOM元素。
然后,我们使用useImperativeHandle
来定义子组件向父组件暴露的接口。在这个例子中,我们暴露了focusInput
和getValue
两个方法。focusInput
方法用于将焦点聚焦到输入框,getValue
方法用于获取输入框的值。
接下来,我们在父组件ParentComponent
中使用useRef
来创建一个childRef
引用,用于获取子组件的实例。
最后,在handleButtonClick
方法中,我们可以通过childRef.current
来访问子组件的方法focusInput
和getValue
,从而直接操作子组件。
这样,当我们点击按钮时,子组件的输入框会聚焦,并且弹出框会显示输入框的值。
这就是useImperativeHandle
的作用,它允许我们在使用React Hooks时,通过定义子组件向父组件暴露的接口,实现直接操作子组件的能力。
8、useInsertionEffect
useInsertionEffect
是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用 useEffect 或者 useLayoutEffect。因此不在赘述。
9、useLayoutEffect
useLayoutEffect
可能会影响性能。尽可能使用 useEffect。useLayoutEffect
是 useEffect 的一个版本,在浏览器重新绘制屏幕之前触发。
10、useMemo
useMemo
是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。
const cachedValue = useMemo(calculateValue, dependencies)
参数
-
calculateValue
:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。React 将会在首次渲染时调用该函数;在之后的渲染中,如果dependencies
没有发生变化,React 将直接返回相同值。否则,将会再次调用calculateValue
并返回最新结果,然后缓存该结果以便下次重复使用。 -
dependencies
:所有在calculateValue
函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中 配置了 React,它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成[dep1, dep2, dep3]
这种形式。React 使用 Object.is 将每个依赖项与其之前的值进行比较。
返回值
在初次渲染时,useMemo
返回不带参数调用 calculateValue
的结果。
在接下来的渲染中,如果依赖项没有发生改变,它将返回上次缓存的值;否则将再次调用 calculateValue
,并返回最新结果。
用法
跳过代价昂贵的重新计算;跳过组件的重新渲染;记忆另一个 Hook 的依赖;记忆一个函数
示例
import React, { useState, useMemo } from 'react';
const Example = () => {
const [num1, setNum1] = useState(0);
const [num2, setNum2] = useState(0);
const sum = useMemo(() => {
console.log('计算和');
return num1 + num2;
}, [num1, num2]);
return (
<div>
<div>
<label>数字1:</label>
<input
type="number"
value={num1}
onChange={(e) => setNum1(parseInt(e.target.value))}
/>
</div>
<div>
<label>数字2:</label>
<input
type="number"
value={num2}
onChange={(e) => setNum2(parseInt(e.target.value))}
/>
</div>
<p>和:{sum}</p>
</div>
);
};
export default Example;
结果
先在数字1处输入01,值改变开始计算,再在数字2处输入00,值未变化,未计算返回原来的值
注释
在上面的示例中,我们使用useState来创建两个状态变量num1和num2,分别表示输入的两个数字。然后,我们使用useMemo来缓存计算的和。useMemo接受一个依赖数组作为第二个参数,当依赖项发生变化时,才会重新计算和。在这个例子中,我们将num1和num2作为依赖项,只有当它们发生改变时,和的计算才会触发。
我们可以在组件中看到一个显示和的p元素。在每次num1或num2改变时,我们可以在控制台中看到"计算和"的日志。但是,如果我们只改变一个数字,另一个数字保持不变,那么和的计算将从缓存中获取,并且不会重新计算。
通过这个例子,我们可以验证useMemo的作用,它确实可以帮助我们缓存计算结果,并在依赖项没有改变时重复使用这些结果。这有助于提高React组件的性能。
11、useReducer
useReducer
是一个 React Hook,它允许你向组件里面添加一个 reducer。
const [state, dispatch] = useReducer(reducer, initialArg, init?)
参数
reducer
:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。initialArg
:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的init
参数。- 可选参数
init
:用于计算初始值的函数。如果存在,使用init(initialArg)
的执行结果作为初始值,否则使用initialArg
。
返回值
useReducer
返回一个由两个值组成的数组:
- 当前的 state。初次渲染时,它是
init(initialArg)
或initialArg
(如果没有init
函数)。 - dispatch 函数。用于更新 state 并触发组件的重新渲染。
用法
管理全局的状态,通过reducer可以在不同的组件获取到state里面的值,无需父子传值和组件订阅,数据在不同组件中传值次数过多的时候可以使用,减少代码繁琐性,方便管理state值,提升性能。
示例
//首先,我们需要在我们的组件中导入useReducer函数:
import React, { useReducer } from "react";
//然后,我们定义一个reducer函数,它将根据我们的操作类型来更新我们的计数状态:
const reducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
throw new Error();
}
};
//接下来,我们可以在我们的组件中使用useReducer来初始化我们的状态和动作分发函数。
const Counter = () => {
const initialState = { count: 0 };
const [state, dispatch] = useReducer(reducer, initialState);
// 通过dispatch调用不同的操作类型来更新状态
const increment = () => {
dispatch({ type: "INCREMENT" });
};
const decrement = () => {
dispatch({ type: "DECREMENT" });
};
return (
<div>
<h2>计数器</h2>
<p>当前计数值: {state.count}</p>
<button onClick={decrement}>减少</button>
<button onClick={increment}>增加</button>
</div>
);
};
export default Counter;
结果
注释
假设我们正在开发一个计数器应用程序,其中我们可以增加或减少计数值。在这个示例中,我们通过调用dispatch函数来分发不同的操作类型,从而更新我们的状态。使用useReducer,我们可以在一个地方集中处理所有操作,并根据当前状态返回新的状态。
12、useRef
useRef
是一个 React Hook,它能让你引用一个不需要渲染的值。常用于ref全局引用一个值,保证组件更新不会渲染;通过ref操作dom,避免重复创建ref内容,参考官方文档,不在赘述
const ref = useRef(initialValue)
13、useState
useState
是一个 React Hook,它允许你向组件添加一个 状态变量。参考官方文档,不在赘述
const [state, setState] = useState(initialState);
14、useSyncExternalStore
useSyncExternalStore
是一个让你订阅外部 store 的 React Hook。
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
参数
-
subscribe
:一个函数,接收一个单独的callback
参数并把它订阅到 store 上。当 store 发生改变,它应当调用被提供的callback
。这会导致组件重新渲染。subscribe
函数会返回清除订阅的函数。 -
getSnapshot
:一个函数,返回组件需要的 store 中的数据快照。在 store 不变的情况下,重复调用getSnapshot
必须返回同一个值。如果 store 改变,并且返回值也不同了(用 Object.is 比较),React 就会重新渲染组件。 -
可选
getServerSnapshot
:一个函数,返回 store 中数据的初始快照。它只会在服务端渲染时,以及在客户端进行服务端渲染内容的 hydration 时被用到。快照在服务端与客户端之间必须相同,它通常是从服务端序列化并传到客户端的。如果你忽略此参数,在服务端渲染这个组件会抛出一个错误。
返回值
该 store 的当前快照,可以在你的渲染逻辑中使用。
用法
订阅外部 store;订阅浏览器 API ;把逻辑抽取到自定义 Hook;添加服务端渲染支持
示例
// 这是一个第三方 store 的例子,
// 你可能需要把它与 React 集成。
// 如果你的应用完全由 React 构建,
// 我们推荐使用 React state 替代。
let nextId = 0;
let todos = [{ id: nextId++, text: "Todo #1" }];
let listeners: any = [];
export const todosStore = {
addTodo() {
todos = [...todos, { id: nextId++, text: "Todo #" + nextId }];
emitChange();
},
subscribe(listener: any) {
listeners = [...listeners, listener];
return () => {
listeners = listeners.filter((l: any) => l !== listener);
};
},
getSnapshot() {
return todos;
},
};
function emitChange() {
for (let listener of listeners) {
listener();
}
}
import { useSyncExternalStore } from "react";
function TodosApp() {
const todos = useSyncExternalStore(
todosStore.subscribe,
todosStore.getSnapshot
);
return (
<>
<button onClick={() => todosStore.addTodo()}>Add todo</button>
<hr />
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
);
}
export default TodosApp;
结果
点击add增加一条
注释
这是一个使用第三方store的例子。这个store用来管理todo列表。它有一个addTodo方法用来添加新的todo,一个subscribe方法用来订阅store的变化,一个getSnapshot方法用来获取当前的todo列表。在store发生变化时,会调用订阅的监听器函数来更新UI。
- 首先,定义一个nextId变量来跟踪下一个todo的id,以及一个todos数组来存储所有的todos。
- addTodo方法会在todos数组中添加一个新的todo对象,并自增nextId。然后调用emitChange函数来通知所有的监听器。
- subscribe方法接收一个监听器函数作为参数,并将其添加到listeners数组中。返回一个取消订阅的函数,可以用来在不需要监听的时候取消订阅。
- getSnapshot方法返回当前的todos数组。
- emitChange函数会遍历所有的监听器函数,并依次调用它们。
- 在使用该store的组件中,可以使用useSyncExternalStore hook来订阅store的变化并同步数据到组件中。
- 在TodosApp组件中,我们通过调用useSyncExternalStore并传入subscribe和getSnapshot方法来订阅store的变化并获取当前的todos数据。
- 在render函数中,我们渲染一个按钮来添加新的todo。当按钮被点击时,会调用todosStore.addTodo方法来添加新的todo。
- 接下来,我们将todos数组中的每个todo渲染成一个列表项。
15、useTransition
useTransition
是一个让你在不阻塞 UI 的情况下来更新状态的 React Hook。
const [isPending, startTransition] = useTransition()
返回值
useTransition
返回一个由两个元素组成的数组:
isPending
标志,告诉你是否存在待处理的转换。- startTransition 函数 允许你将状态更新标记为转换状态。
ahooks(封装了许多钩子,提升开发效率,推荐使用)
安装
npm install --save ahooks