前端开发 React 组件优化

1. 使用 React.memo 进行组件优化

问题:当父组件重新渲染时,子组件也会重新渲染,即使它的 props 没有变化。

解决方案:使用 React.memo 包裹子组件,让其只在 props 变化时才重新渲染。

示例场景:展示一个显示计数的组件,只有计数值变化时才会重新渲染。

import React, { useState } from 'react';const ChildComponent = React.memo(({ count }) => {console.log('ChildComponent rendered');return <div>Count: {count}</div>;
});function ParentComponent() {const [count, setCount] = useState(0);const [text, setText] = useState('');return (<div><ChildComponent count={count} /><button onClick={() => setCount(count + 1)}>Increase Count</button><inputtype="text"value={text}onChange={e => setText(e.target.value)}placeholder="Type something"/></div>);
}export default ParentComponent;

在这个例子中,即使输入框内容改变,ChildComponent 也不会重新渲染,因为它被 React.memo 包裹,只会在 count 变化时重新渲染。

2. 使用 useCallback 和 useMemo 优化函数和计算

问题:在函数式组件中,每次渲染都会创建新的函数和计算,导致不必要的渲染和性能浪费。

解决方案:使用 useCallback 缓存函数,useMemo 缓存计算结果。

示例场景:避免函数在不必要的情况下被重新创建。

import React, { useState, useCallback, useMemo } from 'react';function ExpensiveComponent({ onClick }) {console.log('ExpensiveComponent rendered');return <button onClick={onClick}>Click me</button>;
}function ParentComponent() {const [count, setCount] = useState(0);const handleClick = useCallback(() => {console.log('button clicked');}, []);const computedValue = useMemo(() => {console.log('computed value');return count * 2;}, [count]);return (<div><ExpensiveComponent onClick={handleClick} /><div>Computed Value: {computedValue}</div><button onClick={() => setCount(count + 1)}>Increase Count</button></div>);
}export default ParentComponent;

在这个例子中,handleClick 和 computedValue 只会在依赖项变化时重新创建,从而避免不必要的渲染和计算。

3. 避免不必要的 Re-render

问题:由于父组件的状态或 props 改变,导致子组件不必要地重新渲染。

解决方案:拆分组件,使用 React.memo 或 shouldComponentUpdate,并确保 key 使用合理。

示例场景:优化子组件渲染逻辑。

import React, { useState } from 'react';const ListItem = React.memo(({ item }) => {console.log('ListItem rendered:', item);return <li>{item}</li>;
});function ParentComponent() {const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);const [newItem, setNewItem] = useState('');const addItem = () => {setItems([...items, newItem]);setNewItem('');};return (<div><ul>{items.map((item, index) => (<ListItem key={index} item={item} />))}</ul><inputvalue={newItem}onChange={(e) => setNewItem(e.target.value)}placeholder="Add new item"/><button onClick={addItem}>Add Item</button></div>);
}export default ParentComponent;

4. 虚拟列表优化长列表渲染

问题:渲染大量列表项会导致页面卡顿。

解决方案:使用虚拟滚动技术(如 react-window、@tanstack/react-virtual)只渲染当前视口中的元素。

示例场景:渲染大量数据时使用虚拟列表优化性能。

import React from 'react';
import { FixedSizeList as List } from 'react-window';const Row = ({ index, style }) => (<div style={style}>Row {index}</div>
);function VirtualizedList() {return (<Listheight={150}itemCount={1000}itemSize={35}width={300}>{Row}</List>);
}export default VirtualizedList;

虚拟滚动的原理如下:

4.1. IntersectionObserver 接口简介

IntersectionObserver 接口是 Intersection Observer API 的一部分,用于异步观察目标元素与其祖先元素或顶级文档视口之间的交叉状态,这里的祖先元素或视口被称为根。

创建 IntersectionObserver 对象时,可以设置监听的可见区域比例。该配置一旦确定便不可更改,因此一个观察者对象只能用于监听指定的变化值。不过,同一个观察者可以监听多个目标元素。

// https://caniuse.com/?search=Intersection%20Observer// 创建一个 IntersectionObserver 实例
var observer = new IntersectionObserver(callback, options);

4.2. IntersectionObserver 支持的参数

4.2.1.  回调函数

当目标元素的可见性发生变化时,观察器会调用回调函数 callback 。通常,该函数会触发两次:一次是元素进入视口,开始可见;另一次是离开视口,开始不可见。

示例代码如下:

// 初始化一个 IntersectionObserver 实例
var observer = new IntersectionObserver(changes => {for (const change of changes) {console.log(change.time); // 状态变化的时间戳console.log(change.rootBounds); // 根元素的矩形区域信息console.log(change.boundingClientRect); // 目标元素的矩形区域信息console.log(change.intersectionRect); // 交叉区域的信息console.log(change.intersectionRatio); // 目标元素的可见比例console.log(change.target); // 被观察的目标元素}
}, {});// 开始监听目标元素
observer.observe(target);// 停止监听目标元素
observer.unobserve(target);// 停止所有监听
observer.disconnect();

在上述代码中,callback 是一个箭头函数,接收一个包含 IntersectionObserverEntry 对象的数组作为参数。每个对象代表一个可见性变化的条目。

IntersectionObserverEntry 对象包含以下属性:

  • time:可见性变化发生的时间戳。

  • target:被观察的目标元素。

  • rootBounds:根元素的矩形区域信息。

  • boundingClientRect:目标元素的矩形区域信息。

  • intersectionRect:目标元素与视口或根元素的交叉区域信息。

  • intersectionRatio:目标元素的可见比例,完全可见时为1,完全不可见时为0或更小。

4.2.2.  配置对象

IntersectionObserver 构造函数的第二个参数是配置对象,其中可设置以下属性:

1. threshold 属性

threshold 决定了在何种交叉比例下触发回调函数。默认值为 [0],表示当交叉比例达到 0 时触发回调。用户可自定义该数组,如 [0, 0.25, 0.5, 0.75, 1] 表示当目标元素可见比例为 0%、25%、50%、75%、100% 时触发回调。

new IntersectionObserver(entries => { /* ... */ }, { threshold: [0, 0.25, 0.5, 0.75, 1] });

2. root 属性和 rootMargin 属性

root 属性指定目标元素所在的滚动容器。rootMargin 用于扩展或缩小根元素的边界,从而影响交叉区域的大小。该属性使用 CSS 语法定义,如 10px 20px 30px 40px,表示四个方向的 margin 值。

var opts = {root: document.querySelector('.container'),rootMargin: "500px 0px"
};var observer = new IntersectionObserver(callback, opts);

4.3. 应用实例

 4.3.1. 懒加载

使用 IntersectionObserver API 可以实现惰性加载,在目标元素进入视口时才加载,从而节省带宽。

function query(selector) {return Array.from(document.querySelectorAll(selector));
}var observer = new IntersectionObserver(function(changes) {changes.forEach(function(change) {var container = change.target;var content = container.querySelector('template').content;container.appendChild(content);observer.unobserve(container);});
});query('.lazy-loaded').forEach(function (item) {observer.observe(item);
});

4.3.2.  无限滚动

实现无限滚动的代码示例如下:

var intersectionObserver = new IntersectionObserver(function (entries) {if (entries[0].intersectionRatio <= 0) return;loadItems(10);console.log('Loaded new items');
});intersectionObserver.observe(document.querySelector('.scrollerFooter'));

4.4. 核心 Hooks 实现

以下是 IntersectionObserver 的核心 Hooks 实现代码:

import { RefObject, useEffect, useState } from 'react';interface Args extends IntersectionObserverInit {freezeOnceVisible?: boolean;
}export const useIntersectionObserver = (elementRef: RefObject<Element>,{ threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false }: Args,
): IntersectionObserverEntry | undefined => {const [entry, setEntry] = useState<IntersectionObserverEntry>();const frozen = entry?.isIntersecting && freezeOnceVisible;const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {setEntry(entry);};useEffect(() => {const node = elementRef?.current;const hasIOSSupport = !!window.IntersectionObserver;if (!hasIOSSupport || frozen || !node) return;const observer = new IntersectionObserver(updateEntry, { threshold, root, rootMargin });observer.observe(node);return () => observer.disconnect();}, [elementRef?.current, JSON.stringify(threshold), root, rootMargin, frozen]);return entry;
};

5. 懒加载组件

问题:页面首次加载时间过长。

解决方案:使用 React.lazy 和 Suspense,按需加载组件,减少首屏渲染时间。

示例场景:按需加载不常用的组件。

import React, { Suspense } from 'react';const LazyComponent = React.lazy(() => import('./LazyComponent'));function App() {return (<Suspense fallback={<div>Loading...</div>}><LazyComponent /></Suspense>);
}export default App;

6. 优化资源加载

场景:减少初始资源加载,提升页面性能。

图片懒加载:

<!-- 图片懒加载 -->
<img src="example.jpg" loading="lazy" alt="Lazy loaded image" />

代码分割示例:

// 代码分割示例
import { lazy } from 'react';const SomeComponent = lazy(() => import('./SomeComponent'));

7. 避免不必要的状态更新

问题:频繁更新状态会导致组件重复渲染。

解决方案:优化状态管理逻辑,使用 useReducer 或 Context。

示例场景:使用 useReducer 管理复杂状态。

import React, { useReducer } from 'react';function reducer(state, action) {switch (action.type) {case 'increment':return { count: state.count + 1 };default:return state;}
}function Counter() {const [state, dispatch] = useReducer(reducer, { count: 0 });return (<div><button onClick={() => dispatch({ type: 'increment' })}>Count: {state.count}</button></div>);
}export default Counter;

8. 使用 Profiler 分析性能瓶颈

场景:使用 React Profiler 工具来分析和优化组件渲染。

import React, { Profiler } from 'react';function onRenderCallback(id,phase,actualDuration,baseDuration,startTime,commitTime,interactions
) {console.log({ id, phase, actualDuration });
}function App() {return (<Profiler id="App" onRender={onRenderCallback}><MyComponent /></Profiler>);
}export default App;

这些优化手段在实际项目中,可以根据具体情况选择和组合使用,以达到最佳的性能效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92512.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92512.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

变频器实习DAY12

目录变频器实习DAY12一、继续&#xff0c;柔性平台测试&#xff01;上午 王工Modbus新功能测试下午 柔性平台继续按照说明书再测一遍附加的小知识点中国狸花猫.git文件附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^)变频器实习DAY12 一、继续&#xff0c;柔性平台测试&…

Redis--多路复用

&#x1f9e9; 一、什么是“客户端连接”&#xff1f;所谓 客户端连接 Redis&#xff0c;指的是&#xff1a;一个程序&#xff08;客户端&#xff09;通过网络连接到 Redis 服务端&#xff08;比如 127.0.0.1:6379&#xff09;&#xff0c;建立一个 TCP 连接&#xff0c;双方可…

数组——初识数据结构

一维数组数组的创建数组是一种相同类型元素的集合数组的创建方式C99 中引入了变长数组的概念&#xff0c;变长数组支持数组的大小使用变量来指定明显这里的vs2019不支持变长数组数组初始化和不完全初始化第二个数组就是典型的不完全初始化&#xff0c;开辟了10个空间&#xff0…

技术速递|使用 Semantic Kernel 与 A2A 协议构建多智能体解决方案

作者&#xff1a;卢建晖 - 微软高级云技术布道师 翻译/排版&#xff1a;Alan Wang 在快速发展的 AI 应用开发领域&#xff0c;能够协调多个智能体已成为构建复杂企业级解决方案的关键。虽然单个 AI 智能体擅长特定任务&#xff0c;但复杂的业务场景往往需要跨平台、跨框架甚至跨…

前端跨域请求原理及实践

在前端开发中&#xff0c;"跨域"是一个绕不开的话题。当我们的页面尝试从一个域名请求另一个域名的资源时&#xff0c;浏览器往往会抛出类似Access to fetch at xxx from origin xxx has been blocked by CORS policy的错误。下面将深入探讨跨域请求的底层原理&#…

SpringBoot07-数据层的解决方案:SQL

一、内置数据源 1-1、【回顾】Druid数据源的配置 druid的两种导入格式 1-2、springboot提供的3种内置数据源的配置 若是不配置Druid&#xff0c; springboot提供了3中默认的数据源配置&#xff0c;它们分别是&#xff1a; 1. HikariCP&#xff08;默认&#xff09; 从 Spring…

前端自动化埋点:页面模块级行为跟踪与问题定位系统​​的技术设计方案

一、核心设计目标​​精细化监控​​&#xff1a;定位到页面中​​单个模块​​的曝光、点击等行为。​​低侵入性​​&#xff1a;业务代码与埋点逻辑解耦&#xff0c;降低开发维护成本。​​链路可追踪​​&#xff1a;串联用户从曝光到操作的完整行为路径。​​实时性​​&a…

Node.js 与 Java 性能对比

一、核心架构与任务模型对比Node.js 单线程事件循环 非阻塞I/O 通过V8引擎执行JavaScript&#xff0c;采用事件驱动模型&#xff0c;所有I/O操作&#xff08;如网络请求、文件读写&#xff09;均为非阻塞。单线程处理所有请求&#xff0c;但通过事件循环&#xff08;Event Loo…

Python3常见接口函数

Python3常见接口函数一、基础内置函数 输入输出 print()&#xff1a;输出内容input()&#xff1a;读取用户输入 类型转换 int()、float()、str()、bool()&#xff1a;基础类型转换list()、tuple()、set()、dict()&#xff1a;容器类型转换bin()、hex()、oct()&#xff1a;进制转…

《P4092 [HEOI2016/TJOI2016] 树》

题目描述在 2016 年&#xff0c;佳媛姐姐刚刚学习了树&#xff0c;非常开心。现在他想解决这样一个问题&#xff1a;给定一颗有根树&#xff0c;根为 1 &#xff0c;有以下两种操作&#xff1a;标记操作&#xff1a;对某个结点打上标记。&#xff08;在最开始&#xff0c;只有结…

TCP头部

TCP头部字段详解1. 源端口和目的端口&#xff08;各16位&#xff09;功能&#xff1a;标识发送和接收应用程序范围&#xff1a;0-65535&#xff08;0-1023为知名端口&#xff09;技术细节&#xff1a;客户端通常使用临时端口&#xff08;1024-65535&#xff09;服务端使用固定端…

LinkedList与链表(单向)(Java实现)

引入链表结构&#xff1a;在ArrayList任意位置插入或者删除元素时&#xff0c;就需要将后序元素整体往前或者往后 搬移&#xff0c;时间复杂度为O(n)&#xff0c;效率比较低&#xff0c;因此ArrayList不适合做任意位置插入和删除比较多的场景。因此&#xff1a;java集合中又引入…

网络--VLAN技术

目录 VLAN实验报告 一、实验拓扑 二、实验要求 三、实验思路 1、实验准备 2. VLAN 3. DHCP 自动分配 4、 全网可达验证 四、实验步骤 &#xff08;一&#xff09;交换机配置- VLAN 创建与接口划分 &#xff08;二&#xff09;路由器配置&#xff08;R1&#xff0c…

网络基础17--设备虚拟化

一、传统MSTPVRRP的不足传统MSTPVRRP设计&#xff1a;规划复杂&#xff1a;需要详细规划VRRP多实例的Master归属、MSTP的VLAN和生成树实例归属&#xff0c;以及IP网段。收敛速度慢&#xff1a;故障恢复速度一般在秒级&#xff0c;VRRP收敛时间至少需要3秒&#xff0c;故障恢复速…

深入解析Hadoop资源隔离机制:Cgroups、容器限制与OOM Killer防御策略

Hadoop资源隔离机制概述在分布式计算环境中&#xff0c;资源隔离是保障多任务并行执行稳定性的关键技术。Hadoop作为主流的大数据处理框架&#xff0c;其资源管理能力直接影响集群的吞吐量和任务成功率。随着YARN架构的引入&#xff0c;Hadoop实现了计算资源与存储资源的解耦&a…

static 关键字的 特殊性

static 关键字的 “特殊性” 主要体现在其与类、对象的绑定关系&#xff0c;以及由此带来的一些反常规规则&#xff0c;具体如下&#xff1a;生命周期与内存位置特殊静态成员&#xff08;变量 / 方法&#xff09;随类加载而创建&#xff0c;随类卸载而销毁&#xff0c;生命周期…

win10系统Apache以 FastCGI方式运行PHP

文件下载及官方网站 VC运行库Latest下载页:Latest supported Visual C Redistributable downloads | Microsoft Learnapache httpd官网:Welcome! - The Apache HTTP Server Project下载页:Apache VS17 binaries and modules downloadphp官网:PHP: Hypertext Preprocessor下载页…

MCP与企业数据集成:ERP、CRM、数据仓库的统一接入

MCP与企业数据集成&#xff1a;ERP、CRM、数据仓库的统一接入 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是我…

【milvus检索】milvus检索召回率

Milvus中两种核心查询方式&#xff1a;暴力搜索&#xff08;Brute-force Search&#xff09; 和 近似最近邻搜索&#xff08;Approximate Nearest Neighbor, ANN&#xff09;。 逐一计算相似度&#xff1a;这是暴力搜索&#xff0c;能保证100%找到最相似的向量&#xff0c;但速…

docker Neo4j

Day 1 &#xff1a;Docker Desktop 基础熟悉 运行官方 hello-world 测试&#xff1a; docker -run hello-world 运行 Nginx 体验容器暴露端口&#xff1a; docker run -d -p 8080:80 nginx -d --detach 以 分离模式 运行容器 -p --publish 设置 宿主机与容器的端口映射。…