1、React.memo
定义:
是 React
提供的一个高阶组件(Higher Order Component
),用于优化函数组件的性能。可以避免不必要的渲染,从而提升性能。
作用:
在函数组件之间进行浅比较,如果组件的 props
没有发生变化,则避免重新渲染组件。
注意:
React.memo
默认使用浅比较,所以它只能检测props
的值是否相等,无法检测props
内部的深层次变化。- 如果函数组件的
props
是引用类型的(对象、数组),并且他们在每次渲染时都是新的实例,React.memo
可能无法工作,此时可以用useMemo
来确保传递给子组件的props
是相同的引用。 - 可以使用第二个参数(一个自定义的比较函数,包含两个参数,分别是
prevProps
和nextProps
)来进行精细比较。
例:
const DemoComponent = (props) => <div>{ props.value }</div>;
const MemoizedComponent = React.memo(DemoComponent, (prevProps, nextProps) => {
// 返回 true 表示 props 相等,不重新渲染
// 返回 false 表示 props 不相等,重新渲染
return prevProps.value === nextProps.value;
})
2、useMemo
定义:
用于在函数组件中进行过性能优化。用于缓存和记忆计算结果,避免在每次渲染时重复计算相同的值,从而提升组件性能。
使用:useMemo
有两个参数:
- 第一个参数是一个回调函数,该函数在每次渲染时都会被执行,你可以在这个函数中进行计算、处理逻辑或者其他操作,然后返回计算结果。
- 第二个参数是一个依赖数组,包含影响计算结果的变量或值,当依赖数组中的值发生变化时,
useMemo
会从新计算结果。如果依赖数组为空,则只在首次渲染时执行一次。
const memoizedValue = useMemo(() => {
// 计算或处理逻辑
return computedValue;
}, [dependencies]);
场景:
- 避免重复计算:如果是一个计算耗时较长且不会频繁变化,可以使用
useMemo
来缓存结果,避免每次渲染时都重新计算。 - 优化子组件的渲染:将计算结果作为
props
传递给子组件,可以避免在子组件渲染过程中重复执行相同的计算。 避免不必要的副作用:如果你需要在渲染期间执行副作用(数据请求、订阅等),可以在
useMemo
中进行处理,可以确保副作用只在依赖项变化时执行。3、useCallback
定义:
用于优化函数组件的性能,他的主要作用是缓存函数,避免在每次渲染时重新创建新函数实例,从而减少不必要的渲染。
注意:
使用 useMemo
时,你可以将一个函数和一个依赖数组作为函数传递给他,它会返回一个经过优化的函数。这个函数会在依赖数组中的值发生变化的时候重新创建,否则会保持相同的引用。
使用:useCallback
有两个参数:
callbackFunction
:需要缓存的回调函数。dependencies
:一个数组,包含影响回调函数是否需要重新创建的值。当数组中值发生变化时,useCallback
会重新创建回调函数。如果数组为空,回调函数只会在首次渲染时创建一次。
const callbackFunction = () => {
// 回调函数逻辑
};
const memoizedCallback = useCallback(callbackFunction, [dependencies])
使用场景:
- 避免子组件不必要的重新渲染:将回调函数传递给子组件是,可以使用
useCallback
来确保子组件不会因为父组件重新渲染而重新创建回调函数。 - 避免不必要的副作用:如果回调函数包含副作用(数据请求、订阅),使用
useCallback
可以确保副作用只在依赖项变化时执行。
缺陷:
- 内存消耗:使用
useCallback
会导致函数的缓存,这意味着每当依赖发生变化,都会创建一个新的函数引用,如果在渲染期间频繁创建大量的函数,可能会增加内存消耗。 - 逻辑复杂:
useCallback
适用于缓存简单的回调函数,如果需要缓存具有复杂逻辑的函数,可能会导致代码变得难以理解和维护。 - 额外的函数调用:使用
useCallback
可以避免在每次渲染时重新创建函数,但在某些情况下,可能会导致额外的函数调用,因为记忆化函数的引用发生变化时,使用该函数的组件可能会重新渲染,即使依赖项没有真正发生变化。
避免使用匿名函数:
- 重新渲染的触发:每次父组件重新渲染时,如果传递给子组件的
props
中包含匿名函数,这些匿名函数会被重新创建,导致子组件的重新渲染,即使其他props
没有变化。 - 额外的内存消耗:匿名函数的创建会导致额外的内存开销,每次渲染都会生成新的函数实例,可能在大型应用中累积成性能问题。
- 性能下降:由于匿名函数的频繁创建和销毁,可能会导致页面的性能下降,尤其是在需要频繁渲染的情况下。
4、useRef
定义:
用于在函数组件中存储和访问可变的引用值。与 useState
不同,useRef
不会引发组建的重新渲染,主要用于处理与界面渲染无关的数据。
用法:
const refContainer = useRef(initialValue);
refContainer
:一个包含current
属性的对象,current
属性的值为initialValue
。initialValue
:作为初始值传递给useRef
的值。
用途:
- 保留引用:
useRef
用于保留引用,例如:保存DOM
元素的引用或其他不需要触发重新渲染的值。 - 获取最新值:由于
useRef
的值在重新渲染之间保持稳定,它长用于存储任何需要在渲染周期之间保持不变的数据。 - 触发非渲染副作用:
useRef
可以用于触发非渲染的副作用,例如获取上一次渲染的状态或引用。
例:
import React, { useRef, useEffect } from "react";
function Timer() {
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterVal(() => {
console.log("Timer tick");
}, 1000);
retturn () => {
clearInterVal(intervalRef.current);
}
}, []);
return <div>Timer Component</div>;
}
在上面的例子中,intervalRef
用于存储定时器的引用,以便在组件卸载时清除定时器。由于 useRef
的值在重新渲染之间保持稳定,即使组件重新渲染,intervalRef
的值仍然是上一次渲染时的引用。
5、useLayoutEffect
定义:
类似于 useEffect
,在组件渲染完成后,但在浏览器执行下一次绘制之前同步调用副作用函数。
useLayoutEffect
与 useEffect
的区别:
useEffect
是组件渲染完成后异步调用副作用的函数。useLayoutEffect
是在DOM
更新之后,浏览器绘制之前的同步调用副作用函数。好处是可以更加方便的修改DOM
,获取DOM
信息,这样浏览器只会绘制一次,可以避免浏览器再次回流和重绘。
如果你在 useEffect
的初始化渲染中修改了展示的数据或者 css
样式,那么很有可能会重复渲染导致闪屏,可以使用 useLayoutEffect
。
useLayoutEffect
在 useEffect
之前执行。
用途:
由于 useLayoutEffect
的副作用函数是同步执行的,它会阻塞浏览器的渲染过程。在 useLayoutEffect
中进行了 DOM
操作或读取 DOM
布局信息,会在浏览器进项下一次绘制之前立即生效,使得 useLayoutEffect
适合执行需要 DOM
布局信息的副作用操作,例如:测量元素的尺寸或者位置,根据结果进行相应的操作。
例:
import React, { useState, useLayoutEffect } from "react";
function MyComponent() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
function updateWidth() {
const element = document.getElementById("my-element");
if(element) {
setWidth(element.offsetWidth);
}
};
window.addEventListener("resize", updateWidth);
updateWidth();
return () => {
window.removeEventListener("resize", updateWidth);
};
}, []); // 依赖数组为空,只在组件挂载和卸载的时候执行一次;
return (
<div>
<p>width: { width }</p>
<div id="my-element">Some content</div>
</div>
)
}
上述示例中,useLayoutEffect
用于测量元素的宽度,并在窗口大小发生变化时更新宽度。副作用函数中用了 DOM
操作来获取元素的宽度,并使用 useState
来更新组件状态。
6、React.Fragment
定义:
是 React
提供的一个组件,用于在不引入多余的 DOM
元素的情况下包裹多个子元素。主要作用在 JSX
中创建一个“虚拟”的父容器,用于包裹多个子元素,而不会在最终渲染的 DOM
结构中添加额外的节点。
例:
return <React.Fragment>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</React.Fragment>
// 简化 ↓↓↓
return <>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</>
优点:
减少 DOM
结构:使用 React.Fragment
可以避免在最终渲染的 DOM
中添加多余的父元素,减少不必要的 DOM
。
不影响样式和布局:由于 React.Fragment
不会在渲染中生成,所以不影响样式和布局。
提高性能:不生成额外的 DOM
元素,有助于提高性能。
语法简洁:简化写法,易阅读。
7、React.lazy
定义:
是 React
提供的特性函数,用于实现组件的懒加载,优化性能。允许你在需要的时候才加载组件,而不是初始加载时将所有的代码都打包在一起。
例:
const MyComponent = React.lazy(() => import("./MyComponent"));
说到懒加载,不得不提一下 Suspense。
Suspense 是一个组件,它可以包裹在需要异步加载的组件外部,以指定在加载组件时如何显示加载状态或者备用内容。使用 Suspense 的时候可以延迟组件的渲染,知道实际需要再出现。
例:
import React, { Suspense } from "react";
function App() {
return (
<div>
<Suspense fallback={<div> loading... </div>}>
<MyComponent/>
</Suspense>
</div>
)
}
当页面渲染时,如果 MyComponent 没有加载完成,Suspense 将会显示 fallback 中定义的加载状态,知道组件加载完成。
React.lazy() 和 Suspense 目前只支持默认导出的组件。如果需要导入命名导出的组件,你可以这样:
const MyComponent = React.lazy(() => import("./MyComponent")).then(module => ({ default: module.MyComponent}));
例:
import AnotherComponent from "./another-component";
// 延迟加载不是立即需要的组件
const MUITooltip = React.lazy(() => import("./tooltip"));
function Tooltip({ children, title }) => {
return (
<React.Suspense fallback={ children }>
<MUITooltip title={title}>
{ children }
</MUITooltip>
</React.Suspense>
)
}
function Component(props) {
return (
<Tooltip>
<AnotherComponent/>
</Tooltip>
)
}
8、useContext
定义:
用于在函数组件中访问 React
的上下文(context
)。上下文是一种在组件树中共享数据的机智,可以避免数据通过 props
层层传递给深层组件,建华数据共享和传递的过程。
使用:
const MyContext = React.createContext();
<MyContext.Provider value={/*传递的内容*/}>
<App/>
</MyContext>
// 在子组件中使用上下文值
function ChildComponent() {
const value = useContext(MyContext);
}
用途:
- 共享数据:
useContext
允许你在组件树中共享数据,而无需通过props
传递每一个中间组件。 - 避免
prop drilling
:通过useContext
,你可以避免在组件层次结构中进行多层级的props
传递。 - 主题和用户设置:可用于实现主题切换,用户设置等全局状态共享。
- 解耦组件:
useContext
是组件不再直接依赖于特定数据源,增加组建的可复用性和灵活性。
问题:Context
是粗颗粒度的,只要其中一个值发生变化都会更新用到该 Context
的组件。解决办法是在需要 Context
内容值进行细分,通过 props
按需传递。
必要时也要做缓存,避免更新。
import React, { useState, useMemo } from "react";
const App = () => {
const [visible, setVisible] = useState(false);
const EditorContextVal = useMemo(() => ({ visible, setVisible }), [visible, setVisible]);
return (
<EditorContext.Provider value={EditorContextVal}>
<Editor />
</EditorContext.Provider>
)
}
9、useTransition
定义:
用于在并发模式(Concurrent Mode
)下,处理 UI
过度的一种方式,允许在异步操作中实现平滑的 UI
过度效果,无需在加载期间完全阻塞用户界面。对于加载资源、处理网络请求等很有用,可以保证页面的响应性。
startTransition
告诉 React
一些状态更新具有较低优先级,即每个其他状态更新或 UI
渲染触发器,具有较高的优先级。
使用:
const [isPending, startTransition] = useTransition();
isPending
: 是否有过度正在进行。startTransition
:用于触发过渡,接受一个回调函数作为参数,这个回调函数中的操作会被视为异步操作,可以触发 UI 过渡。
场景:
- 异步操作:当需要在进行异步操作时,能够在过渡期间保持界面的响应,避免页面卡顿。
- 平滑过渡:当异步操作会导致页面元素的变化,希望在数据加载期间进行平滑的界面过渡效果。
例:
import { useTransition, setState } from "react";
function App() {
const [isPending, startTransition] = useTransition();
const [filterTerm, setFilterTerm] = useState("");
const filteredProducts = filterProducts(filterTerm);
function updateFilterHandler(event) {
startTransition(() => {
setFilterTerm(event.target.value);
})
}
return (
<div id="app">
<input type="text" onChange={updateFilterHandler} />
{ isPending && <p style={{ color: "white" }}> Updating list.. </p>}
<ProductList products={filteredProducts} />
</div>
)
}
14、useDefferdValue
定义:
用于在并发模式(Concurrent Mode
)下处理 UI
过渡的方式,通过 useDefferdValue
允许变量延时更新,同事接受一个可选的延迟更新最大值。React
将尝试尽快更新延迟值,如果在给定的 timeoutMs
期限内未能完成,他将强制更新。
const defferValue = useDeferredValue(value, { timeoutMs: 1000 });
useDeferredValue 可以在并发渲染时调整优先级,用于延迟计算逻辑比较复杂的状态,其他组件优先渲染,等待这个状态更新完再渲染。
作用:
useDefferedValue 和 useTransition 一样,都是用于不阻塞 UI 的情况下更新状态,但是使用场景不同。 useTransition 是昂给你能够完全控制哪个 更新操作 应该以一个比较低的优先级被调用。(从父级传递来的状态可能有偏差。
例:
我们有一个 0 - 19999 的数组,以列表的形式展示,文本框输入的内容用来过滤列表。
import { useState, useMemo, useDeferredValue } = form "react";
const numbers = { ...new Array(200000).keys() };
// 父组件
export default function App() {
const [query, setQuery] = useSttate("");
const handdleChange = (e) => {
setQuery(e.target.value);
};
return (
<div>
<input type={number} onChange={handleChange} value={query}
<List query={query} />
</div>
)
}
// 子组件
function List(props) {
const { query } = props;
const defQuery = useDeferredValue(query);
const list = useMemo(
() =>
numbers.map((i, index) =>
defQuery ? (
i.toString().startsWith(defQuery) && <p key={index}>{i}</p>
) : (
<p key={index}>{i}</p>
),
),
[defQuery],
);
return <div>{list}</div>;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。