我曾经阅读过官方文档,文档中出现过很多新的概念名词,这些概念对于新手或者从其他方向转型过来的朋友非常难理解,这是我的React Hooks 大白话总结,希望帮到大家
useCallback
在组件内定义一个函数声明,这个函数在每次创建组件的时候,是否重新创建取决于两个因素:
1、从未声明创建过函数
2、依赖项参数是值是否发生变化
这里两个条件都会立即触发重新创建函数,如果没有依赖项,则每次返回和之前一样的函数。
变通的理解:你可以认为useCallback包裹的函数,是仅针对当前组件内的函数缓存,可以通过key(依赖项的值,依赖项依然还是传入参数,请不要被这个比喻而误导)的变化引发缓存的刷新 ,无论组件被循环创建多少次,或者组件被销毁重新创建,该函数不会重新创建,会从历史缓存里直接调用,只有依赖项的值发生变化,才会重新创建。
这个hook 解决了内存中函数在组件重绘多次重复创建的问题,下面这个例子由于 useCallback
依赖 productId
与 referrer
自上次渲染后始终没有发生改变,因此 handleSubmit
也没有改变。由于 handleSubmit
没有发生改变,ShippingForm
就跳过了重新渲染。
javascript
import { useCallback } from 'react';
import ShippingForm from './ShippingForm.js';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
function post(url, data) {
// 想象这发送了一个请求
console.log('POST /' + url);
console.log(data);
}
useContext
这是一个面相父子祖孙多代组件树形结构的数据同步hook,首先需要通过
javascript
import { createContext, useContext } from 'react';
export const myContext = createContext(null);//导出很重要
来创建一个上下文对象(官方文档并没有在一开始就提起),当然作者把他叫做context我认为有歧义,叫做useContextStore更合适,就像useCallback 叫做useFunctionCache不是更容易理解,
此时在代码中可以用
javascript
const [testValue,setTestValue]=useState("我是一只快乐的小鸟")
export default function MyApp() {
//provider 顾名思义为服务提供者,他是myContext提供了的上下文存储的服务组件,
//可以直接接收一个value,这个值就是我们要共享的值
return (<myContext.provider value={testValue}>
<child1/>
<child2/>
<child3/>
<child4/>
</myContext.provider>)
}
此时,myContext.provider包裹下的所有组件,如child1、child2、child3、child4 ,同时包括child{$n}的后代组件,都可以直接通过useContext 来获取myContext 绑定的值,这个值,可以通过
setTestValue("世界更美好")
的方式直接进行修改,后代组件可以通过useContext的方式获取到共享到myContext的值,如下:
ini
const myContextValue = useContext(myContext);
useDebugValue
useDebugValue
用于在开发过程中提供更好的调试信息。它通常在自定义钩子函数内部使用,用于暴露一些有用的值,以便在 React 开发者工具中查看。
useDebugValue
接受两个参数:value
和 format
。value
是要暴露的值,而 format
是一个可选的格式化函数,用于将值呈现为更可读的形式。
useDebugValue
的主要目的是让开发者能够在 React 开发者工具中查看有关自定义钩子函数内部状态的信息。例如,当你在自定义钩子函数中使用了某个状态变量,并且你希望在 React 开发者工具中能够看到该变量的值,你可以使用 useDebugValue
来实现这一点。
以下是一个简单的示例,展示了 useDebugValue
的用法:
javascript
import { useDebugValue, useState } from 'react';
function useCustomHook() {
const [count, setCount] = useState(0);
// 使用 useDebugValue 暴露 count 的值
useDebugValue(count);
const increment = () => {
setCount(count + 1);
};
return { count, increment };
}
function MyComponent() {
const { count, increment } = useCustomHook();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
在上面的示例中,useCustomHook
是一个自定义钩子函数,它使用了 useDebugValue
来暴露 count
的值。这样,在 React 开发者工具中,你就可以看到 count
的当前值,并且能够更好地调试和理解自定义钩子函数的状态。
useDeferredValue
这是一个延迟更新值得hook,官方文档描述的非常复杂且难以理解,实际上其功能非常实用且简单
实际上,当我们的程序对被useDeferredValue包裹起来的值进行连续修改时,当后台修改触发渲染,当后台未渲染完成,发生了第二次修改,则上一次渲染会被直接中断被抛弃,直到最后一次完整触发渲染,界面上的内容会被更新,这里往往需要配合<Suspense fallback={
Loading...
}>来使用,Suspense 可以在后台进程修改的时候作为占位符提示来使用。
官方明确指出,他与防抖节流是不同的,防抖节流往往需要配合时间间隔的原理来实现,useDeferredValue不同之处在于,其与react深度结合,其渲染效率取决于用户机器的性能,性能越高,延迟几乎不会被看出,每一次修改值可能都会在界面上体现,性能越低的电脑,看到的延迟效果越强烈。
useEffect
在 React 中,"副作用" 是指在组件函数内部执行的与组件渲染结果无关的操作。如:数据订阅和取消订阅、DOM操作、发送网络请求、与第三方库集成、计时器操作等与React无关的操作,在 useEffect 内部,你可以执行副作用操作,并在必要时进行清理,以确保在组件生命周期内正确管理副作用。
官方文档里对两个参数的描述是setup函数和依赖项列表,setup函数我们可以理解为回调函数,实际上就是最终会被调用的函数,依赖项首先可以理解为传入参数,其次可以理解为可影响setup函数再次调用的触发条件,当依赖项值发生变化的时候,setup函数会被重新调用,所以setup函数内的return 是需要返回一个销毁函数的,避免旧的对象未被清理,调用时机可以这么理解:
-
将组件挂载到页面时,将运行 setup 代码。
-
重新渲染 依赖项 变更的组件后:
- 首先,使用旧的 props 和 state 运行 cleanup 代码。//cleanup 会执行return的函数内容,进行销毁操作。
- 然后,使用新的 props 和 state 运行 setup 代码。
如以下代码
javascriptimport { useEffect, useRef } from 'react'; export default function ModalDialog({ isOpen, children }) { const ref = useRef(); //isOpen作为依赖项,当值发生改变时,触发一个setup函数 useEffect(() => { if (!isOpen) { return; } const dialog = ref.current; dialog.showModal(); return () => { //这里是给提供的清理操作,也可以理解为销毁函数 dialog.close(); }; }, [isOpen]); return <dialog ref={ref}>{children}</dialog>; }
在这个例子中,外部系统是
animation.js
中的动画库。它提供了一个名为FadeInAnimation
的 JavaScript 类,该类接受一个 DOM 节点作为参数,并暴露start()
和stop()
方法来控制动画。此组件 使用 ref 访问底层 DOM 节点。Effect 从 ref 中读取 DOM 节点,并在组件出现时自动开启该节点的动画,所以依赖项并不是必须的。javascript//这是一个动画控制的示例 import { useState, useEffect, useRef } from 'react'; import { FadeInAnimation } from './animation.js'; function Welcome() { const ref = useRef(null); useEffect(() => { const animation = new FadeInAnimation(ref.current); animation.start(1000); return () => { //这里是给提供的清理操作,也可以理解为销毁函数 animation.stop(); }; }, []); return ( <h1 ref={ref} style={{ opacity: 0, color: 'white', padding: 50, textAlign: 'center', fontSize: 50, backgroundImage: 'radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%)' }} > Welcome </h1> ); } export default function App() { const [show, setShow] = useState(false); return ( <> <button onClick={() => setShow(!show)}> {show ? 'Remove' : 'Show'} </button> <hr /> {show && <Welcome />} </> ); }
useId
useId很简单,只是一个为当前组件生成一个唯一id而存在,请勿将他用来生成数据列表的id,数据列表的id应该由数据服务方来生成。
此外useId还可以配置全局前缀,代码如下:
javascriptconst app = createRoot(document.getElementById('app'), { identifierPrefix: 'my-first-app-' //这里来修改前缀 }); app.render(<App />);
useImperativeHandle
这是一个不常用的hook,作用是让子组件将自身的ref自定义函数操作暴露给父组件。
必须使用forwardRef,这样才会被父组件访问到
javascriptimport { forwardRef } from 'react'; const MyInput = forwardRef(function MyInput(props, ref) { return <input {...props} ref={ref} />; });
不要滥用 ref
你应当仅在你没法通过 prop 来表达 命令式 行为的时候才使用 ref:例如,滚动到指定节点、聚焦某个节点、触发一次动画,以及选择文本等等。
如果可以通过 prop 实现,那就不应该使用 ref
例如,你不应该从一个
Model
组件暴露出{open, close}
这样的命令式句柄,最好是像<Modal isOpen={isOpen} />
这样,将isOpen
作为一个 prop。副作用 可以帮你通过 prop 来暴露一些命令式的行为。完整的例子
父组件 ParentComponent 使用 useRef 创建一个 childRef,并将其传递给 ChildComponent 的 ref 属性。当点击父组件中的按钮时,调用 childRef.current.focus() 来触发子组件中的 focus 方法,从而将焦点设置到子组件的输入框上。
javascriptimport { useRef, useImperativeHandle, forwardRef } from 'react'; // 子组件 const ChildComponent = forwardRef((props, ref) => { const inputRef = useRef(null); // 定义暴露给父组件的方法 useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input type="text" ref={inputRef} />; }); // 父组件 const ParentComponent = () => { const childRef = useRef(null); const handleClick = () => { childRef.current.focus(); }; return ( <div> <ChildComponent ref={childRef} /> <button onClick={handleClick}>Focus Child</button> </div> ); };
useMemo
当依赖项不变时,被useMemo包裹的函数不会重复执行,会从缓存中直接响应,减少了不必要的重复计算,提高了计算效率。
示例
当需要对一个数字进行平方计算时,可以使用 useMemo 对计算结果进行记忆化处理。这样,在依赖项变化时才重新执行计算,避免不必要的重复计算。
typescriptimport { useMemo, useState } from 'react'; function SquareCalculator() { const [number, setNumber] = useState(0); const squaredNumber = useMemo(() => { return number ** 2; }, [number]); return ( <div> <input type="number" value={number} onChange={(e) => setNumber(Number(e.target.value))} /> <p>Square of {number} is: {squaredNumber}</p> </div> ); }
useState
声明一个状态变量,会返回一个由两个值组成的数组的值,分别为:
可以使用如下方式接收和调用:
javascriptimport { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> You pressed me {count} times </button> ); }
useReducer
useReducer
和useState
非常相似,但是它可以让你把状态更新逻辑从事件处理函数中移动到组件外部,作为全局store来使用,并且可以同时处理多个字段。示例
javascriptimport { useReducer } from 'react'; function reducer(state, action) { if (action.type === 'incremented_age') { return { age: state.age + 1 }; } throw Error('Unknown action.'); } export default function Counter() { const [state, dispatch] = useReducer(reducer, { age: 42 }); return ( <> <button onClick={() => { //值得注意的是 reducer是只读函数,需要通过 dispatch 来修改指定的值,传入的参数为action dispatch({ type: 'incremented_age' }) }}> Increment age </button> <p>Hello! You are {state.age}.</p> </> ); }
useRef
useRef 通常是用来操作DOM节点的,当然官方自身定义是为了实现一个与渲染无关的值的声明,需要通过ref.current 的方式来取值,有点被代理的意思,但我们大多数情况下还是用来才做DOM的引用,毕竟用来声明一个值是有点多此一举了。
示例
javascriptimport { useRef } from 'react'; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Focus the input </button> </> ); }
useTransition
使用
useTransition
可以帮助我们在异步操作期间提供更好的用户体验,通过平滑的过渡效果来避免应用的卡顿和阻塞。useTransition
返回一个数组,其中包含两个元素:startTransition
和isPending
。startTransition
是一个函数,用于触发异步操作的开始,并在操作完成后更新组件以及isPending的值,目前据我了解,对promise then语法支持良好。isPending
是一个布尔值,表示异步操作是否仍在进行中。
示例
通过
useTransition
,我们获取到startTransition
和isPending
。当用户点击按钮时,调用startTransition
并传入一个回调函数,该函数用于执行异步操作。在回调函数中,我们可以执行需要的异步操作,例如数据加载。按钮的
disabled
属性根据isPending
的值来决定,以防止用户在异步操作进行中重复点击。Suspense
组件用于包裹异步加载的组件,并提供一个fallback
属性,用于在异步操作进行中显示一个加载中的提示。javascriptimport { useTransition, Suspense } from 'react'; const MyComponent = () => { const [startTransition, isPending] = useTransition(); const fetchData = () => { startTransition(() => { // 异步操作开始 // 可以在这里进行数据加载等异步操作 }); }; return ( <div> <button onClick={fetchData} disabled={isPending}> {isPending ? 'Loading...' : 'Fetch Data'} </button> <Suspense fallback={<div>Loading...</div>}> {/* 渲染异步加载的组件 */} </Suspense> </div> ); };
文章中如果有错误,请及时指出,感谢点赞