useCallback(fn, dependencies)
useCallback是一个React Hook,它允许您在重新渲染之间缓存函数定义。
const cachedFn = useCallback(fn, dependencies)
1、参数:
fn:要缓存的函数值。它可以接受任何参数并返回任何值。React将在初始渲染期间返回(而不是调用!)您的函数。在下一次渲染中,如果自上次渲染以来依赖项没有更改,React将再次为您提供相同的函数。否则,它将为您提供在当前渲染过程中传递的函数,并将其存储起来,以备以后重用。React不会调用您的函数。函数将返回给您,以便您可以决定何时以及是否调用它。
dependencies:fn代码内部引用的所有反应值的列表。反应值包括props、state以及直接在组件体内声明的所有变量和函数。如果您的linter配置为React,它将验证每个React值是否正确指定为依赖项。依赖项列表必须具有恒定数量的项,并且像[dep1,dep2,dep3]一样以内联方式编写。React将使用Object.is的比较算法将每个依赖项与其以前的值进行比较。
2、返回值:
在初始渲染时,useCallback返回您传递的fn函数。
在随后的渲染过程中,它将从上次渲染中返回一个已经存储的fn函数(如果依赖项没有更改),或者返回您在此渲染过程中传递的fn函数。
3、告诫:
useCallback是一个Hook,所以您只能在组件的顶层或您自己的Hook中调用它。不能在循环或条件内部调用它。如果需要,请提取一个新组件并将状态移动到其中。
4、用法:
在以下渲染中,React将把依赖项与您在上一次渲染中传递的依赖项进行比较。如果任何依赖项都没有更改(与Object.is相比),useCallback将返回与以前相同的函数。否则,useCallback将返回在此渲染中传递的函数。
import { useCallback } from 'react';export default function ProductPage({ productId, referrer, theme }) {const handleSubmit = useCallback((orderDetails) => {post('/product/' + productId + '/buy', {referrer,orderDetails,});}, [productId, referrer]);
默认情况下,当组件重新渲染时,React会递归地重新渲染其所有子级。这就是为什么当ProductPage使用不同的主题重新渲染时,ShippingForm组件也会重新渲染。这对于不需要太多计算即可重新渲染的组件来说很好。但是,如果验证了重新渲染的速度较慢,则可以通过将其包装在备忘录中,告诉ShippingForm在其道具与上次渲染相同时跳过重新渲染
function ProductPage({ productId, referrer, theme }) {// ...return (<div className={theme}><ShippingForm onSubmit={handleSubmit} /></div>);
使用了memo包裹子组件只有prop变化时才会重新渲染,否则就跳过重新渲染
import { memo } from 'react';const ShippingForm = memo(function ShippingForm({ onSubmit }) {// ...
});
在JavaScript中,函数(){}或()=>{}总是创建不同的函数,类似于{}对象文字总是创建新对象的方式。通常情况下,这不会是一个问题,但这意味着ShippingForm道具将永远不会相同,您的备忘录优化也不会起作用。在这个例子中父组件的prop变化时函数会重新创建,即使子组件使用memeo依然不起作用,因为handleSubmit每次都不同
function ProductPage({ productId, referrer, theme }) {// Tell React to cache your function between re-renders...const handleSubmit = useCallback((orderDetails) => {post('/product/' + productId + '/buy', {referrer,orderDetails,});}, [productId, referrer]); // ...so as long as these dependencies don't change...return (<div className={theme}>{/* ...ShippingForm will receive the same props and can skip re-rendering */}<ShippingForm onSubmit={handleSubmit} /></div>);
}
现在我们就可以将他等着在useCallback,只有依赖项变化时才会返回新的函数定义。一般情况下我们不必将函数使放在useCallback中,除非处于某种特定原因
function ProductPage({ productId, referrer, theme }) {// Tell React to cache your function between re-renders...const handleSubmit = useCallback((orderDetails) => {post('/product/' + productId + '/buy', {referrer,orderDetails,});}, [productId, referrer]); // ...so as long as these dependencies don't change...return (<div className={theme}>{/* ...ShippingForm will receive the same props and can skip re-rendering */}<ShippingForm onSubmit={handleSubmit} /></div>);
}
5、useCallack和useMemo 的区别
useMemo缓存调用函数的结果。useCallback缓存函数本身。
const requirements = useMemo(() => { // Calls your function and caches its resultreturn computeRequirements(product);}, [product]);const handleSubmit = useCallback((orderDetails) => { // Caches your function itselfpost('/product/' + productId + '/buy', {referrer,orderDetails,});}, [productId, referrer]);
可以想象成这样:
// Simplified implementation (inside React)
function useCallback(fn, dependencies) {return useMemo(() => fn, dependencies);
}
6、何时使用useCallack
如果你的应用程序像这个网站,并且大多数交互都很粗糙(比如替换一个页面或整个部分),那么通常不需要记忆。另一方面,如果你的应用程序更像一个绘图编辑器,并且大多数交互都是细粒度的(比如移动的形状),那么你可能会发现记忆非常有用。
使用useCallback缓存函数只有在少数情况下才有价值:
1、您将函数作为餐厨传递给了memo包裹的子组件
2、您传递的函数稍后将用作某个Hook的依赖项。例如,包装在useCallback中的另一个函数依赖于它,或者您依赖于useEffect中的此函数。
在其他情况下,用useCallback包装函数没有任何好处。这样做也没有重大危害,所以一些团队选择不考虑个别案例,并尽可能多地记忆。缺点是代码的可读性变差。此外,并非所有的记忆都是有效的:一个“总是新的”值就足以破坏整个组件的记忆。
useContext(SomeContext)
useContext是一个React Hook,它允许您从组件中读取和订阅上下文。
import { useContext } from 'react';function MyComponent() {const theme = useContext(ThemeContext);// ...
1、参数
SomeContext:您之前使用createContext创建的上下文。
2、返回值
useContext返回调用组件的上下文值,返回的值始终是最新的
3、告诫
相应的<Context.Provider>需要位于执行useContext()调用的组件之上。
4、用法
深层数据传递、通过context传递更新数据
react 会找桑钱上下文最近的provider,不论层级多深都可以使用useContext(ThemeContext),访问到
function MyPage() {return (<ThemeContext.Provider value="dark"><Form /></ThemeContext.Provider>);
}function Form() {// ... renders buttons inside ...
}
useEffect(setup, dependencies?)
useEffect是一个React Hook,用于将组件与外部系统同步。
import { useEffect } from 'react';
import { createConnection } from './chat.js';function ChatRoom({ roomId }) {const [serverUrl, setServerUrl] = useState('https://localhost:1234');useEffect(() => {const connection = createConnection(serverUrl, roomId);connection.connect();return () => {connection.disconnect();};}, [serverUrl, roomId]);// ...
}
1、参数
setup:具有Effect逻辑的函数。您的设置函数也可以选择返回一个清理函数。
可选依赖项:设置代码内部引用的所有反应值的列表。反应值包括props、state以及直接在组件体内声明的所有变量和函数。
2、用法
连接到外部系统:某些组件在页面上显示时,需要保持与网络、某些浏览器API或第三方库的连接。这些系统不受React控制,所以它们被称为外部系统。
例如:
一个由setInterval()和clearInterval.()管理的计时器。
使用window.addEventListener()和window.removeEventLister()的事件订阅。
带有API的第三方动画库,如animation.start()和animation.reset()
3、解释
感觉官网的解释有些晦涩,一般我们是这么理解的
可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
情况一:不传第二个参数(模拟 class 组件的 DidMount 和 DidUpdate )
useEffect(() => {
/** 执行逻辑 */
})
情况二:传递一个空数组(模拟 class 组件的 DidMount )
useEffect(() => {
/** 执行逻辑 */
},[])
情况三:传递数组有依赖项(模拟 class 组件的 DidUpdate )
useEffect(() => {
/** 执行逻辑 */
},[age,name])
情况四:第一个参数可以返回一个回调函数(模拟 WillUnMount 组件销毁的时候 停止计时器 )
const [time, setTime] = useState(0)
useEffect(() => {const InterVal = setInterval(() => {setTime(time + 1)},1000)return () => {clearInterval(InterVal )}
},[])
useMemo(calculateValue, dependencies)
useMemo是一个React Hook,可以在重新渲染之间缓存计算结果。
import { useMemo } from 'react';function TodoList({ todos, tab }) {const visibleTodos = useMemo(() => filterTodos(todos, tab),[todos, tab]);// ...
}
1、参数
calculateValue:计算要缓存的值的函数。
dependencies:calculateValue代码内部引用的所有反应值的列表。
2、返回值
在初始呈现时,useMemo返回不带参数的调用calculateValue的结果。
在接下来的渲染过程中,它将返回上次渲染中已经存储的值(如果依赖项没有更改),或者再次调用calculateValue,并返回calculateValue返回的结果。
3、用法
跳过昂贵的计算
4、怎样算昂贵的计算
一般来说,除非您正在创建或循环数千个对象,否则这可能并不昂贵。如果你想获得更多的信心,你可以添加一个控制台日志来测量在一段代码中花费的时间:
console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');
如果记录的总时间加起来相当大(比如1毫秒或更长),那么将计算结果记忆起来可能是有意义的。作为实验,您可以将计算结果封装在useMemo中,以验证该交互的总记录时间是否减少。
5、何时使用useMemo
1、计算较慢,并且依赖值很少变化
2、将该函数结果作为参数传给memo包裹的子组件
3、该结果作为其他hook的依赖项
在其他情况下,将计算包装在useMemo中没有任何好处。这样做也没有重大危害,所以一些团队选择不考虑个别案例,并尽可能多地记忆。这种方法的缺点是代码的可读性变差
useState(initialState)
useState是一个React Hook,它允许您将状态变量添加到组件中。
import { useState } from 'react';function MyComponent() {const [age, setAge] = useState(28);const [name, setName] = useState('Taylor');const [todos, setTodos] = useState(() => createTodos());// ...
1、参数
initialState:您希望状态初始为的值。它可以是任何类型的值,但函数有一种特殊的行为。此参数在初始渲染后将被忽略。
如果将函数作为initialState传递,它将被视为初始值设定项函数。
2、返回值
current state :当前状态,在第一次渲染期间,它将与您通过的initialState相匹配。
set函数:用于将状态更新为不同的值并触发重新渲染。您可以直接传递下一个状态,也可以传递根据上一个状态计算的函数
setName('Taylor');setAge(a => a + 1);
3、用法
向组件添加状态,React将存储下一个状态,使用新值再次渲染组件,并更新UI。
import { useState } from 'react';function MyComponent() {const [age, setAge] = useState(42);const [name, setName] = useState('Taylor');// ...
4、定义对象
您可以将对象和数组置于状态。在React中,状态被认为是只读的,所以您应该替换它,而不是突变现有的对象。
// 🚩 Don't mutate an object in state like this:
form.firstName = 'Taylor';
// ✅ Replace state with a new object
setForm({...form,firstName: 'Taylor'
});
5、避免传入函数调用结果为初始值
const [todos, setTodos] = useState(createInitialTodos());
尽管createInitialTodos()的结果仅用于初始渲染,但您仍在每次渲染时调用此函数。如果创建大型数组或执行昂贵的计算,这可能是浪费。
const [todos, setTodos] = useState(createInitialTodos);
要解决此问题,可以将其作为初始值设定项函数传递给useState您传递的是函数本身createInitialTodos,而不是调用它的结果createInitialTodos()。如果您将函数传递给useState,React只会在初始化期间调用它。
useRef(initialValue)
useRef是一个React Hook,它允许您引用渲染不需要的值。
mport { useRef } from 'react';function MyComponent() {const intervalRef = useRef(0);const inputRef = useRef(null);// ...
1、参数
initialValue:您希望ref对象的当前属性初始为的值。它可以是任何类型的值。此参数在初始渲染后将被忽略。
2、返回值
current:最初,它被设置为您传递的initialValue。您可以稍后将其设置为其他内容。
3、用法
①使用ref定义一个无需引起页面重新渲染的状态
改变一个ref的值不会触发重新渲染。这意味着引用非常适合存储不影响组件视觉输出的信息。
function handleStopClick() {const intervalId = intervalRef.current;clearInterval(intervalId);
}
②使用ref操作DOM
使用ref来操作DOM是特别常见的。React对此有内置支持。
首先,声明一个初始值为null的ref对象:
import { useRef } from 'react';function MyComponent() {const inputRef = useRef(null);// ...return <input ref={inputRef} />;
React创建DOM节点并将其放在屏幕上后,React将把ref对象的当前属性设置为该DOM节点。现在,您可以访问<input>的DOM节点,并调用focus()等方法:
③ 避免重新创建引用内容
React将初始ref值保存一次,并在下次渲染时忽略它。
function Video() {const playerRef = useRef(new VideoPlayer());
尽管新VideoPlayer()的结果仅用于初始渲染,但您仍在每次渲染时调用此函数。如果创建昂贵的对象,这可能是浪费。
要解决此问题,您可以按如下方式初始化ref:
function Video() {const playerRef = useRef(null);if (playerRef.current === null) {playerRef.current = new VideoPlayer();}// ...
4、将ref传给自定义组件
默认情况下,组件不会将其DOM节点公开给父组件,这么写会报错
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
如果您希望MyInput的父组件能够访问<input>DOM节点,则必须选择使用forwardRef:
import { forwardRef } from 'react';const MyInput = forwardRef(({ value, onChange }, ref) => {return (<inputvalue={value}onChange={onChange}ref={ref}/>);
});export default MyInput;
useImperativeHandle(ref, createHandle, dependencies?)
1、解释
React的useImperativeHandle是自定义钩子,可以让父组件获取并执行子组件内某些自定义函数(方法),或者获取子组件的状态。
2、本质
useImperativeHandle本质上其实是子组件将自己内部的函数(方法)通过useImperativeHandle添加到父组件中useRef定义的对象中。如果想使用useImperativeHandle,那么还要结合useRef、React.forwardRef一起使用。
3、示例
结合这个例子:
使用forwardRef后父组件可以访问子组件的Dom
import { forwardRef } from 'react';const MyInput = forwardRef(({ value, onChange }, ref) => {return (<inputvalue={value}onChange={onChange}ref={ref}/>);
});export default MyInput;
假设您不想公开整个<input>DOM节点,但希望公开它的两个方法:focus和scrollIntoView。要做到这一点,请将真实的浏览器DOM保存在一个单独的引用中。然后使用useImperativeHandle只使用您希望父组件调用的方法来公开句柄:
import { forwardRef, useRef, useImperativeHandle } from 'react';const MyInput = forwardRef(function MyInput(props, ref) {const inputRef = useRef(null);useImperativeHandle(ref, () => {return {focus() {inputRef.current.focus();},scrollIntoView() {inputRef.current.scrollIntoView();},};}, []);return <input {...props} ref={inputRef} />;
});
现在,如果父组件获得对MyInput的引用,它将能够调用其上的焦点和scrollIntoView方法。但是,它将无法完全访问底层的<input>DOM节点。