
- 横轴代表时间的流逝,纵轴代表
React需要执行的步骤(render,commit)
React Hooks
Hooks related to performance:
- useCallback, useMemo, useTransition, useDeferredValue, useOptimistic
- useLayoutEffect, useInsertionEffect
useId
- Notable: 不应该用来生成 List
key - 使用- 无障碍属性生成 unique ID
- 使用- 为多个元素生成
ID - 使用- 为
Batch ID指定共享前缀 - 使用- 服务端和客户端使用相同的前缀
useInsertionEffect
- vs.
render或useLayoutEffect中注入样式 - 直接在
JS代码中编写样式,而不是编写CSS文件.- 使用编译器静态提取到
CSS文件 - 内联样式
- 运行时注入 style
计算 - 浏览器频繁地重新计算样式
慢 -useInsertionEffect可以解决
- 使用编译器静态提取到
useCallback
-
useCallback的dependency是使用 Object.is() 进行浅比较. -
useCallback目的是提高React 的性能,防止re-render. 一般使用存在2个场景- 函数作为一个 prop 传递给子组件,子组件是一个
slowRenderComponent; 该函数作为prop传递给React.memo包裹的子组件 - 函数作为 useEffect
dependency. 也可以使用useCallback包裹 function, 防止re-render.
- 函数作为一个 prop 传递给子组件,子组件是一个
export interface IFilterState {
[key: string]: string[] | string;
}
useDeferredValue
主要用于优化渲染性能, 避免多次更新状态和重新渲染组件
和debounce的区别是: useDeferredValue 适用于渲染, debounce 使用于接口的调用.
和useTransition的区别是: useDeferredValue 作用于 state, startTransition 可以包裹一段同步程序, 作用于set函数,
useLayoutEffect
useLayoutEffect等body中代码运行完,再进行repaint
scene1: 计算元素的位置.比如说 tooltip
scene2: re-direct 到别的页面(换句话说,如果没有权限, 当前页面休想看见一点)
if (campaign_id && ad_id) {
location.href = `${XXX}? aadvid=${aadvid}`;
}
useMemo
useMemo + React.memo 支持跳过子组件的 re-render/expensive calculation
useRef
- 不要在
渲染期间写入或者读取ref.current,也就是不要将ref.current写在函数之中, 放在Event handler或useEffect中 - 当
React创建dom节点并将其渲染到屏幕时,React 将会把 DOM 节点设置为 ref 对象的current属性 - 避免重复创建
DOM - 使用
ref引用一个值, 间隔器
export default VideoComponent() {
const playerRef = useRef(null);
const getPlayer = () => {
if (playerRef.current !== null) {
return playerRef.current;
}
const player = new VideoPlayer();
playerRef.current = player;
return player;
}
useState
React 的心智模型
useSyncExternalStore
Tearing React 18之前渲染是不可以中断的,因此会出现卡顿现象,在18中增加了lane和时间分片(5ms)那渲染是可以中断的,那在用户执行输入的过程中,页面展示的状态是状态不一致.Redux 8使用useSyncExternalStore强制一致,但是当你停止操作,状态渲染出来是一致的.
import { useSyncExternalStore } from 'react';
export default function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
function getSnapshot() {
return navigator.onLine;
}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
useTransition
useTransition是一个帮助你在不阻塞UI的情况下,更新状态的React Hook
- 异步数据加载和渲染过渡
- 将状态更新标记为
非阻塞的 Transition - 建议; 构建一个
Suspense-enabled路由: 如果你正在构建一个React框架或路由,建议将页面导航标记为转换效果
6.12.0react-router增加了对useTransition的支持
const [isPending, startTransition] = useTransition()
const [tab, setTab] = useState('about');
function handleSelectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
- startTransition 中需要包裹一个异步函数,
setXXState(). [如果异步更新一个value,请使用 useDeferredValue] - 将当前选项卡更新状态放在一个过渡中,
startTransition中包裹的 state 更新允许被打断. (e.g.: 将选项卡的切换标记为transition state, 切换A后立即切换成 B, A 的渲染就会被打断)
- 转换效果可以被中断,在渲染期间可以和页面其他地方进行交互;也就是渲染期间点击页面,页面会给到反应
- 转换效果可以防止不必要的加载指示符
Troubleshooting
- 确保
startTransition中的代码是同步运行的.setTimeout不能放在startTransition中,await也不能放在startTransition中 - 传递给
startTransition的函数会立即运行,在React运行的时候会使用transition标记
APIs
- 一般API
createContext, forwardRef, use - 性能优化相关
React.lazy, React.memo, React.startTransition
lazy
-
lazy能够让你在组件第一次被渲染之前,延迟加载组件的代码 -
lazy(load)load function
import { lazy } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
function Editor() {
// ...
}
startTransition
startTransition(scope)
startTransition 可以让你在不阻塞 UI 的情况下更新 state. 将state标记为Transition渲染
vs useTransition 不提供 isPending 状态来跟踪一个 transition 是否正在进行
import { startTransition } from 'react';
function TabContainer() {
const [tab, setTab] = useState('about');
// 在回调函数中标记 transition
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
memo
- React 通过
浅比较来比较旧的和新的props. - 可以指定自定义比较函数
arePropsEqual, 我们必须比较每一个props, 包括函数.deepClone可能变得非常缓慢.
use
use API 可以读类Promise或者 Context 中的值. use 可以替代useContext
React Dom
- API: ReactDOM.createRoot, ReactDOM.hydrateRoot, ReactDOM.createPortal
- related to performance: ReactDOM.flushSync
- <div/> 通用组件
createRoot
-
onRecoverableError适用于组件中可能出现可恢复性错误的场景 - an app fully built with React 使用一次
createRoot, modal 使用createPortal - a page partially built with React 使用 多次
const navDomNode = document.getElementById('navigation');
const navRoot = createRoot(navDomNode);
navRoot.render(<Navigation />);
const commentDomNode = document.getElementById('comments');
const commentRoot = createRoot(commentDomNode);
commentRoot.render(<Comments />);
-
onRecoverableError重试机制尝试重新加载, 注意使用ErrorBoundary
可以恢复,就重试一次尝试恢复,不可以就展示ErrorBoundary的后备UI
我们可以尝试使用try/catch块捕获异常,并在onRecoverableError函数中进行自定义的错误处理逻辑
- 网络通信错误
- 图片加载错误
createPortal
利用
ReactDOM.createPortal提供一个独立于主体的Dom
createPortal允许你将 JSX 作为 children 渲染至 DOM 的不同部分。
- portal 只改变 DOM 节点的所处位置
- 使用
createPortal渲染model -
Portal中的事件传播, 遵循React树而不是DOM
- 利于
TabList布局, inputBar 和Tab放在一起布局,将ref传递到下面的子组件
<div ref={searchRef} />
<VideoList
currentTab={currentTab}
getSearchContainer={() => searchRef.current}
/>
React synthetic Event:
有些事件,如 onAbort 和 onLoad,在浏览器中不冒泡,但是在 React 中仍然会冒泡
提供了与原生事件一致的: nativeEvent
与原生 DOM 事件标准一致,也提供了而为的属性和方法