- 横轴代表时间的流逝,纵轴代表
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
事件标准一致,也提供了而为的属性和方法