1.memo
memo允许组件在 props 没有改变的情况下跳过重新渲染默认通过Object.is比较每个prop,可通过第二个参数,传入自定义函数来控制对比过程
const Chart = memo(function Chart({ dataPoints }) {
// ...
}, arePropsEqual);
function arePropsEqual(oldProps, newProps) {
return (
oldProps.dataPoints.length === newProps.dataPoints.length &&
oldProps.dataPoints.every((oldPoint, index) => {
const newPoint = newProps.dataPoints[index];
return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;
})
);
}
2.useMemo
在每次重新渲染的时候能够缓存计算的结果
import { useState, useMemo } from "react";
function App() {
const [count, setCount] = useState(0);
const memoizedValue = useMemo(() => {
//创建1000位数组
const list = new Array(1000).fill(null).map((_, i) => i);
//对数组求和
const total = list.reduce((res, cur) => (res += cur), 0);
//返回计算的结果
return count + total;
//添加依赖项,只有count改变时,才会重新计算
}, [count]);
return (
<div>
{memoizedValue}
<button onClick={() => setCount((prev) => prev + 1)}>按钮</button>
</div>
);
}
export default App;
3.useMemo
缓存函数的引用地址,仅在依赖项改变时才会更新
import { useState, memo } from 'react';
const App = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((prev) => prev + 1);
};
return (
<div>
{count}
<MyButton handleClick={handleClick} />
</div>
);
};
const MyButton = memo(function MyButton({ handleClick }: { handleClick: () => void }) {
console.log('子组件渲染');
return <button onClick={handleClick}>按钮</button>;
});
export default App;
点击按钮,可以发现即使子组件使用memo包裹了,但还是更新了,控制台打印出“子组件渲染”。这是因为父组件App每次更新时,函数handleClick每次都返回了新的引用地址,因此对于子组件来说每次传入的都是不一样的值,从而触发重渲染。
同样的,减少使用通过内联函数绑定事件。每次父组件更新时,匿名函数都会返回一个新的引用地址,从而触发子组件的重渲染.
<MyButton handleClick={() => setCount((prev) => prev + 1)} />
使用useCallback可以缓存函数的引用地址,将handleClick改为
const handleClick = useCallback(()=>{
setCount(prev=>prev+1)
},[])
再点击按钮,会发现子组件不会再重新渲染。
4.useTransition
使用useTransition提供的startTransition来标记一个更新作为不紧急的更新。这段任务可以接受延迟或被打断渲染,进而去优先考虑更重要的任务执行页面会先显示list2的内容,之后再显示list1的内容
import { useState, useEffect, useTransition } from "react";
const App = () => {
const [list1, setList1] = useState<null[]>([]);
const [list2, setList2] = useState<null[]>([]);
const [isPending, startTransition] = useTransition();
useEffect(() => {
startTransition(() => {
//将状态更新标记为 transition
setList1(new Array(10000).fill(null));
});
}, []);
useEffect(()=>{
setList2(new Array(10000).fill(null));
},[])
return (
<>
{isPending ? "pending" : "nopending"}
{list1.map((_, i) => (
<div key={i}>{i}</div>
))}
-----------------list2
{list2.map((_, i) => (
<div key={i}>6666</div>
))}
</>
);
};
export default App;
5、useDeferredValue
可以让我们延迟渲染不紧急的部分,类似于防抖但没有固定的延迟时间
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
6、Fragment
当呈现多个元素而不需要额外的容器元素时,使用React.Fragment可以减少DOM节点的数量,从而提高呈现性能
const MyComponent = () => {
return (
<React.Fragment>
<div>Element 1</div>
<div>Element 2</div>
<div>Element 3</div>
</React.Fragment>
);
};
7、合理使用Context
Context 能够在组件树间跨层级数据传递,正因其这一独特机制,Context 可以绕过 React.memo 或 shouldComponentUpdate 设定的比较过程。也就是说,一旦 Context 的 Value 变动,所有使用 useContext 获取该 Context 的组件会全部 forceUpdate。即使该组件使用了memo,且 Context 更新的部分 Value 与其无关
为了使组件仅在 context 与其相关的value发生更改时重新渲染,将组件分为两个部分。在外层组件中从 context 中读取所需内容,并将其作为 props 传递给使用memo优化的子组件。
8、尽量避免使用index作为key
在渲染元素列表时,尽量避免将数组索引作为组件的key。如果列表项有添加、删除及重新排序的操作,使用index作为key,可能会使节点复用率变低,进而影响性能使用数据源的id作为key
const MyComponent = () => {
const items = [{ id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }, { id: 3, name: "Item 3" }];
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};
9、懒加载
通过React.lazy和React.Suspense实施代码分割策略,将React应用细分为更小的模块,确保在具体需求出现时才按需加载相应的部分
定义路由
import { lazy } from 'react';
import { createBrowserRouter } from 'react-router-dom';
const Login = lazy(() => import('../pages/login'));
const routes = [
{
path: '/login',
element: <Login />,
},
];
//可传第二个参数,配置base路径 { basename: "/app"}
const router = createBrowserRouter(routes);
export default router;
引用路由
import { Suspense } from 'react';
import { RouterProvider } from 'react-router-dom';
import ReactDOM from 'react-dom/client';
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<Suspense fallback={<div>Loading...</div>}>
<RouterProvider router={router} />
</Suspense>,
);
10、组件卸载时的清理
在组件卸载时清理全局监听器、定时器等。防止内存泄漏影响性能
import { useState, useEffect, useRef } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const timer = useRef<NodeJS.Timeout>();
useEffect(() => {
// 定义定时器
timer.current = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
const handleOnResize = () => {
console.log('Window resized');
};
// 定义监听器
window.addEventListener('resize', handleOnResize);
// 在组件卸载时清除定时器和监听器
return () => {
clearInterval(timer.current);
window.removeEventListener('resize', handleOnResize);
};
}, []);
return (
<div>
<p>{count}</p>
</div>
);
}
export default MyComponent;
附:
React 性能优化十大总结
@[toc]
1. 引言
为什么需要 React 性能优化?
React 是一个高效的前端框架,但在复杂应用中,性能问题仍然可能出现。通过性能优化,可以提升应用的响应速度和用户体验。
React 性能优化的基本概念
React 性能优化主要关注减少不必要的渲染、优化 DOM 操作、减少内存占用等方面。
2. React 性能优化的十大方法
1. 使用 React.memo 优化组件渲染
React.memo 是一个高阶组件,用于缓存组件的渲染结果,避免不必要的重新渲染。
const MyComponent = React.memo(function MyComponent(props) {
// 组件逻辑
});
2. 使用 useMemo 和 useCallback 缓存计算结果和函数
useMemo 用于缓存计算结果,useCallback 用于缓存函数,避免在每次渲染时重新计算或创建。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
3. 使用 React.lazy 和 Suspense 实现代码分割
React.lazy 和 Suspense 可以实现组件的懒加载,减少初始加载时间。
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
4. 使用 shouldComponentUpdate 或 PureComponent 避免不必要的渲染
shouldComponentUpdate 和 PureComponent 可以避免组件在 props 或 state 未变化时重新渲染。
class MyComponent extends React.PureComponent {
render() {
// 组件逻辑
}
}
5. 使用 key 优化列表渲染
为列表项设置唯一的 key,可以帮助 React 识别哪些项发生了变化,减少不必要的 DOM 操作。
const listItems = items.map(item => (
<li key={item.id}>{item.name}</li>
));
6. 使用 React.Fragment 减少不必要的 DOM 节点
React.Fragment 可以避免在渲染时添加额外的 DOM 节点。
function MyComponent() {
return (
<React.Fragment>
<ChildA />
<ChildB />
</React.Fragment>
);
}
7. 使用 useReducer 替代 useState 管理复杂状态
useReducer 可以更好地管理复杂的状态逻辑,减少状态更新的次数。
const [state, dispatch] = useReducer(reducer, initialState);
8. 使用 React.memo 和 useContext 优化上下文传递
通过 React.memo 和 useContext,可以避免在上下文变化时重新渲染所有子组件。
const MyComponent = React.memo(function MyComponent() {
const value = useContext(MyContext);
// 组件逻辑
});
9. 使用 React.memo 和 useRef 优化 DOM 操作
useRef 可以保存 DOM 引用,避免在每次渲染时重新获取 DOM 元素。
const myRef = useRef(null);
useEffect(() => {
myRef.current.focus();
}, []);
10. 使用 React.memo 和 useEffect 优化副作用
通过 React.memo 和 useEffect,可以避免在每次渲染时执行不必要的副作用。
const MyComponent = React.memo(function MyComponent() {
useEffect(() => {
// 副作用逻辑
}, [dependency]);
// 组件逻辑
});
3. 实战:在 React 项目中应用性能优化
项目初始化
使用 Create React App 创建一个新的 React 项目:
npx create-react-app my-react-app
cd my-react-app
npm start
使用 React.memo 优化组件渲染
在 src/components/MyComponent.js 中使用 React.memo 优化组件渲染:
import React from 'react';
const MyComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});
export default MyComponent;
使用 useMemo 和 useCallback 缓存计算结果和函数
在 src/components/MyComponent.js 中使用 useMemo 和 useCallback:
import React, { useMemo, useCallback } from 'react';
function MyComponent({ a, b }) {
const memoizedValue = useMemo(() => a + b, [a, b]);
const memoizedCallback = useCallback(() => {
console.log(a, b);
}, [a, b]);
return (
<div>
<p>{memoizedValue}</p>
<button onClick={memoizedCallback}>Click me</button>
</div>
);
}
export default MyComponent;
使用 React.lazy 和 Suspense 实现代码分割
在 src/App.js 中使用 React.lazy 和 Suspense:
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./components/LazyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
export default App;
使用 shouldComponentUpdate 或 PureComponent 避免不必要的渲染
在 src/components/MyComponent.js 中使用 PureComponent:
import React, { PureComponent } from 'react';
class MyComponent extends PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}
export default MyComponent;
使用 key 优化列表渲染
在 src/components/MyList.js 中使用 key 优化列表渲染:
import React from 'react';
function MyList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default MyList;
使用 React.Fragment 减少不必要的 DOM 节点
在 src/components/MyComponent.js 中使用 React.Fragment:
import React from 'react';
function MyComponent() {
return (
<React.Fragment>
<ChildA />
<ChildB />
</React.Fragment>
);
}
export default MyComponent;
使用 useReducer 替代 useState 管理复杂状态
在 src/components/MyComponent.js 中使用 useReducer:
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
export default MyComponent;
使用 React.memo 和 useContext 优化上下文传递
在 src/components/MyComponent.js 中使用 React.memo 和 useContext:
import React, { useContext } from 'react';
import MyContext from './MyContext';
const MyComponent = React.memo(function MyComponent() {
const value = useContext(MyContext);
return <div>{value}</div>;
});
export default MyComponent;
使用 React.memo 和 useRef 优化 DOM 操作
在 src/components/MyComponent.js 中使用 React.memo 和 useRef:
import React, { useRef, useEffect } from 'react';
const MyComponent = React.memo(function MyComponent() {
const myRef = useRef(null);
useEffect(() => {
myRef.current.focus();
}, []);
return <input ref={myRef} />;
});
export default MyComponent;
使用 React.memo 和 useEffect 优化副作用
在 src/components/MyComponent.js 中使用 React.memo 和 useEffect:
import React, { useEffect } from 'react';
const MyComponent = React.memo(function MyComponent({ dependency }) {
useEffect(() => {
console.log('Effect triggered');
}, [dependency]);
return <div>{dependency}</div>;
});
export default MyComponent;
4. 进阶:React 性能优化的策略
使用 React.memo 优化组件渲染
通过 React.memo 缓存组件的渲染结果,避免不必要的重新渲染。
使用 useMemo 和 useCallback 缓存计算结果和函数
通过 useMemo 和 useCallback 缓存计算结果和函数,避免在每次渲染时重新计算或创建。
使用 React.lazy 和 Suspense 实现代码分割
通过 React.lazy 和 Suspense 实现组件的懒加载,减少初始加载时间。
使用 shouldComponentUpdate 或 PureComponent 避免不必要的渲染
通过 shouldComponentUpdate 或 PureComponent 避免组件在 props 或 state 未变化时重新渲染。
使用 key 优化列表渲染
为列表项设置唯一的 key,帮助 React 识别哪些项发生了变化,减少不必要的 DOM 操作。
使用 React.Fragment 减少不必要的 DOM 节点
通过 React.Fragment 避免在渲染时添加额外的 DOM 节点。
使用 useReducer 替代 useState 管理复杂状态
通过 useReducer 更好地管理复杂的状态逻辑,减少状态更新的次数。
使用 React.memo 和 useContext 优化上下文传递
通过 React.memo 和 useContext 避免在上下文变化时重新渲染所有子组件。
使用 React.memo 和 useRef 优化 DOM 操作
通过 useRef 保存 DOM 引用,避免在每次渲染时重新获取 DOM 元素。
使用 React.memo 和 useEffect 优化副作用
通过 React.memo 和 useEffect 避免在每次渲染时执行不必要的副作用。
5. 常见问题与解决方案
性能优化的兼容性问题
-
问题:某些旧版浏览器可能不支持 React 的某些功能。
-
解决方案:确保浏览器兼容性,或使用兼容性更好的方法。
性能优化的性能问题
-
问题:频繁操作可能导致性能问题。
-
解决方案:优化操作逻辑,减少不必要的操作。
性能优化的使用误区
-
问题:误用性能优化可能导致逻辑混乱。
-
解决方案:理解性能优化的原理,避免误用。
6. 总结与展望
React 性能优化的最佳实践
-
明确使用场景:根据需求选择合适的性能优化方法。
-
优化性能:合理使用性能优化,避免频繁操作。
-
确保兼容性:确保性能优化在不同浏览器和环境中兼容。
未来发展方向
-
更强大的性能优化:支持更复杂的开发场景。
-
更好的性能优化:提供更高效的实现方式。
通过本文的学习,你应该已经掌握了 React 性能优化的十大方法及实战应用。希望这些内容能帮助你在实际项目中更好地提升应用性能,提升用户体验!