为什么要做性能优化?
示例1:
import React, { useState } from "react";
export default function App() {
const [num, updateNum] = useState(0);
console.log('App render啦');
return (
<div style={{ backgroundColor: '#2196f3', width: '50%', padding: '20px', marginRight: '20px' }}>
<h2>组件1的num:{num}</h2>
<button onClick={() => {updateNum(num + 1);}} style={{ marginBottom: '10px' }}>点我+1</button>
<div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
<Child1 />
<Child2 />
</div>
</div>
);
}
const Child1 = () => {
console.log('组件1-1 render啦')
return (
<div style={{ backgroundColor: '#ffeb3b', height: '300px', width: '50%', marginRight: '20px' }}>
组件1-1
</div>
)
}
const Child2 = () => {
console.log('组件1-2 render啦')
return (
<div style={{ backgroundColor: '#ffeb3b', height: '300px', width: '50%' }}>
组件1-2
</div>
)
}
触发一次按钮之后我们会发现child1和child2的console.log的内容都打印了
这是因为React项目中的任何一个组件发生state状态的变更,React更新机制都会从最顶层的根节点开始往下递归对比,通过双缓存机制判断出哪些节点发生了变化,然后更新节点。这样的更新机制成本并不小,因为在判断过程中,如果React发现 props、state、context任意一个不同,那么就认为该节点被更新了,对比的成本非常小,但是 re-render 的成本偏高。但其实child1和child2中并没有需要更新的内容,这两个组件的渲染是非必要的,他们两个的更新都是有耗时的,存在性能浪费,所以我们要做些性能优化,去避免不必要的渲染。
怎样让他只更新变化了的组件,跳过没有变化的组件?
上面说React发现 props、state、context任意一个不同,那么就认为该节点被更新了,那我们是否可以认为props、state、context是会变化的部分,变化的部分会影响不变的,那我们把变化的部分和不变的部分拆分,让他们互不影响,是否能够做到性能优化呢?
import React, { useState } from "react";
export default function App() {
const [num, updateNum] = useState(0);
console.log('App render啦');
return (
<div style={{ backgroundColor: '#2196f3', width: '50%', padding: '20px', marginRight: '20px' }}>
<UpdateNumCom />
<div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
<Child1 />
<Child2 />
</div>
</div>
);
}
const UpdateNumCom = () => {
console.log('num组件render啦');
const [num, updateNum] = useState(0);
return <>
<h2>组件1的num:{num}</h2>
<button onClick={() => { updateNum(num + 1); }} style={{ marginBottom: '10px' }}>点我+1</button>
</>
}
const Child1 = () => {
console.log('组件1-1 render啦')
return (
<div style={{ backgroundColor: '#ffeb3b', height: '300px', width: '50%', marginRight: '20px' }}>
组件1-1
</div>
)
}
const Child2 = () => {
console.log('组件1-2 render啦')
return (
<div style={{ backgroundColor: '#ffeb3b', height: '300px', width: '50%' }}>
组件1-2
</div>
)
}
发现只有UpdateNumCom组件更新了,其他组件都没有更新,这样拆分的变与不变的部分是可以达成性能优化的。
示例2:
import React, { useState } from "react";
export default function Demo1 (){
console.log('Demo1 render');
const [num, updateNum] = useState(0);
return (
<div title={num + ''}>
<input value={num} onChange={(e)=> updateNum(Number(e.target.value)) } />
<p>num is {num}</p>
<Child1 />
</div>
);
}
function Child1(){
console.log('子组件 render');
return <p>一个组件</p>;
}
最外层div的titile依赖num,这样我们怎样拆分变与不变的部分呢?
import React, { useState } from "react";
export default function Demo1 (){
console.log('Demo1 render');
return (
<Input>
<Child1 />
</Input>
);
}
function Child1(){
console.log('子组件 render');
return <p>一个组件</p>;
}
function Input({children}){
const [num, updateNum] = useState(0);
return (
<div title={num + ''}>
<input value={num} onChange={(e)=> updateNum(Number(e.target.value)) } />
<p>num is {num}</p>
{children}
</div>
);
}
我们可以通过children的方式传参,我们发现这样也可以实现性能优化,这是因为useState是在Input组件中,Demo1没有state(变化的部分),Input内传入的props也不会变化,子孙组件内也没有state不会重新渲染。
通过上面的例子我们发现,不使用性能优化Api我们也可以通过优化组件的手段实现性能优化
但其实我们日常生活中更多的是使用API去实现性能优化,但是什么情况下使用什么API呢,下面介绍一下几个API
性能优化API
React.memo
memo 接受两个参数:
WrapComponent:你要优化的组件;
(prev, next) => boolean:通过对比 prev(旧 props),next(新 props)是否一致,返回 true(不更新)、false(更新);
注意:memo 只针对 props 来决定是否渲染,且是浅比较 示例:
import React, { useState, useCallback } from 'react';
import iEqual from 'fast-deep-equal/react';
const Children = ({value}) => {
console.log('Children render');
return <div>{JSON.stringify(value)}</div>
};
const ChildrenMemo = React.memo(Children, (newProps, oldProps) => iEqual(newProps, oldProps));
export default function Home() {
const [count, setCount] = useState({a:1});
const onClick = useCallback(() => {
setCount({a:2});
},[]);
console.log('Home render');
return (
<div>
<button onClick={onClick}>button {JSON.stringify(count)}</button>
<ChildrenMemo value={count}/>
</div>
);
}
useCallback
useCallback 同样接受两个参数:
callback:传入子组件的函数 deps:相关依赖项数组
最终 useCallback 会把传入的 callback 缓存起来。当 deps 依赖发生改变的时候,会重新缓存最新的 callback ,否则就使用缓存的结果
import React, { useState, useCallback } from 'react';
import iEqual from 'fast-deep-equal/react';
const Children = ({value, onClick}) => {
console.log('Children render');
console.log(onClick,'onClick');
return <div>{JSON.stringify(value)}</div>
};
const ChildrenMemo = React.memo(Children, (pre, next)=>{
console.log(pre, next)
return pre === next;
});
export default function Home() {
const [count, setCount] = useState(1);
const onClick =() => {
setCount(count + 1);
};
console.log('Home render');
return (
<div>
<button onClick={onClick}>button {JSON.stringify(count)}</button>
<ChildrenMemo value={count} onClick={onClick}/>
</div>
);
}
useMemo
useMemo 接受两个参数:
callback:计算结果的执行函数 deps:相关依赖项数组
最终 useMemo 在执行了 callback 后,返回一个结果,这个结果就会被缓存起来。当 deps 依赖发生改变的时候,会重新执行 callback 计算并返回最新的结果,否则就使用缓存的结果。
在日常我们经常会使用上面三个性能优化的API,但是我们为什么可以通过这几个Api实现性能优化呢
性能优化背后的源码运行机制
上面说React发现 props、state、context任意一个不同,那么就认为该节点被更新了,那是怎么进行对比的呢,通过react源码我们来看一下内部是怎么实现的
function beginWork(current, workInProgress, renderLanes) {
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} else {
// Neither props nor legacy context changes. Check if there's a pending
// update or context change.
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
if (!hasScheduledUpdateOrContext && // If this is the second pass of an error or suspense boundary, there
// may not be work scheduled on `current`, so we check for this flag.
(workInProgress.flags & DidCapture) === NoFlags) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
if (getIsHydrating() && isForkedChild(workInProgress)) {
var slotIndex = workInProgress.index;
var numberOfForks = getForksAtLevel();
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent:
{
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case LazyComponent:
{
var elementType = workInProgress.elementType;
return mountLazyComponent(current, workInProgress, elementType, renderLanes);
}
case FunctionComponent:
{
var Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
}
case ClassComponent:
{
var _Component = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;
var _resolvedProps = workInProgress.elementType === _Component ? _unresolvedProps : resolveDefaultProps(_Component, _unresolvedProps);
return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
case ForwardRef:
{
var type = workInProgress.type;
var _unresolvedProps2 = workInProgress.pendingProps;
var _resolvedProps2 = workInProgress.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2);
return updateForwardRef(current, workInProgress, type, _resolvedProps2, renderLanes);
}
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
case Mode:
return updateMode(current, workInProgress, renderLanes);
case Profiler:
return updateProfiler(current, workInProgress, renderLanes);
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
case MemoComponent:
{
var _type2 = workInProgress.type;
var _unresolvedProps3 = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props.
var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3);
{
if (workInProgress.type !== workInProgress.elementType) {
var outerPropTypes = _type2.propTypes;
if (outerPropTypes) {
checkPropTypes(outerPropTypes, _resolvedProps3, // Resolved for outer only
'prop', getComponentNameFromType(_type2));
}
}
}
_resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3);
return updateMemoComponent(current, workInProgress, _type2, _resolvedProps3, renderLanes);
}
case SimpleMemoComponent:
{
return updateSimpleMemoComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes);
}
case IncompleteClassComponent:
{
var _Component2 = workInProgress.type;
var _unresolvedProps4 = workInProgress.pendingProps;
var _resolvedProps4 = workInProgress.elementType === _Component2 ? _unresolvedProps4 : resolveDefaultProps(_Component2, _unresolvedProps4);
return mountIncompleteClassComponent(current, workInProgress, _Component2, _resolvedProps4, renderLanes);
}
case SuspenseListComponent:
{
return updateSuspenseListComponent(current, workInProgress, renderLanes);
}
case ScopeComponent:
{
break;
}
case OffscreenComponent:
{
return updateOffscreenComponent(current, workInProgress, renderLanes);
}
}
}
function updateSimpleMemoComponent(current, workInProgress, Component, nextProps, renderLanes) {
{
if (workInProgress.type !== workInProgress.elementType) {
var outerMemoType = workInProgress.elementType;
if (outerMemoType.$$typeof === REACT_LAZY_TYPE) {
var lazyComponent = outerMemoType;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
outerMemoType = init(payload);
} catch (x) {
outerMemoType = null;
} // Inner propTypes will be validated in the function component path.
var outerPropTypes = outerMemoType && outerMemoType.propTypes;
if (outerPropTypes) {
checkPropTypes(outerPropTypes, nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
'prop', getComponentNameFromType(outerMemoType));
}
}
}
}
if (current !== null) {
var prevProps = current.memoizedProps;
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref && ( // Prevent bailout if the implementation changed due to hot reload.
workInProgress.type === current.type )) {
didReceiveUpdate = false; // The props are shallowly equal. Reuse the previous props object, like we
workInProgress.pendingProps = nextProps = prevProps;
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
workInProgress.lanes = current.lanes;
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
didReceiveUpdate = true;
}
}
}
return updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes);
}
function shallowEqual(objA, objB) {
if (objectIs(objA, objB)) {
return true;
}
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
} // Test for A's keys different from B.
for (var i = 0; i < keysA.length; i++) {
var currentKey = keysA[i];
if (!hasOwnProperty.call(objB, currentKey) || !objectIs(objA[currentKey], objB[currentKey])) {
return false;
}
}
return true;
}
通过打断点发现组件更新进入beginWork中,会判断oldProps !== newProps || hasContextChanged() || workInProgress.type !== current.type,不相等的话会继续更新,相等则不更新,使用memo则会进入SimpleMemoComponent判断,执行updateSimpleMemoComponent方法,执行shallowEqual通过objectIs进行新旧props的对比,如果相同则不会更新,这里需要注意的是如果props是对象,他只会比较第一层的key下的value是否相等。
之前我们通过组件优化或者使用API的形式都可以实现性能优化,现在react19出现,React Compiler 可以让我们不需要手动去进行性能优化,内部便实现了性能优化。
以上是我自己对react性能优化的一些学习,可能表述不是很正确,大家一起沟通学习。