当前位置: 首页 > news >正文

React 渲染深度解密:从 JSX 到 DOM 的初次与重渲染全流程

关键点

  • React 渲染机制:React 通过虚拟 DOM 和 Diff 算法实现高效的初次渲染与重渲染。
  • 初次渲染:从 JSX 到真实 DOM 的转换,涉及 Fiber 架构、调度和协调。
  • 重渲染:组件状态或 Props 变化触发更新,优化 Diff 和渲染性能。
  • 常见问题:包括不必要的重渲染、性能瓶颈和状态管理复杂性。
  • 优化策略:使用 useMemouseCallback、React.memo 和 Fiber 调度优化性能。
  • 实践场景:通过一个复杂的前端项目,展示渲染流程的实现与优化。

引言

React 的渲染机制是其核心竞争力,通过虚拟 DOM 和高效的 Diff 算法,React 能够在状态或 Props 变化时快速更新视图。初次渲染将 JSX 转换为真实 DOM,重渲染则通过协调(Reconciliation)更新变化部分。React 18 引入的 Fiber 架构进一步优化了渲染流程,支持任务分片、优先级调度和并发渲染。然而,开发者常常面临不必要的重渲染、性能瓶颈或状态管理复杂性等问题。

本文通过构建一个基于 React 18 的动态仪表盘应用,全面探讨 React 的初次渲染和重渲染流程。我们将从 JSX 解析开始,逐步分析 Fiber 架构、Diff 算法、调度机制和优化策略,涵盖 TypeScript 集成、可访问性、手机端适配和部署实践。通过丰富的代码示例和场景分析,开发者将深入理解 React 渲染的底层逻辑,掌握优化复杂应用的技巧。

通过本项目,您将学习到:

  • 初次渲染流程:从 JSX 到真实 DOM 的转换,涉及 Fiber 节点的创建和协调。
  • 重渲染机制:状态和 Props 变化触发更新,Diff 算法优化 DOM 操作。
  • 性能优化:使用 useMemouseCallback、React.memo 和并发渲染减少开销。
  • 可访问性:确保动态内容对屏幕阅读器友好。
  • 手机端适配:优化响应式布局和触控交互。
  • 部署:将项目部署到 Vercel,支持高可用性。

本文面向有经验的开发者,假设您熟悉 HTML、CSS、JavaScript、React 和 TypeScript 基础知识。内容详实且实用,适合深入学习 React 渲染机制。


需求分析

在动手编码之前,我们需要明确动态仪表盘应用的功能需求。一个清晰的需求清单能指导开发过程并帮助我们优化渲染流程。以下是项目的核心需求:

  1. 动态仪表盘功能
    • 显示实时数据(如图表、指标卡)。
    • 支持用户交互(如过滤、排序、切换主题)。
    • 使用状态管理(如 React Query)获取和更新数据。
  2. 初次渲染优化
    • 快速完成 JSX 到真实 DOM 的转换。
    • 支持服务器端渲染(SSR)或静态生成(SSG)以加速首屏加载。
  3. 重渲染优化
    • 最小化不必要的重渲染。
    • 使用 useMemouseCallback 优化复杂组件。
    • 应用 React.memo 防止子组件重复渲染。
  4. TypeScript 集成
    • 为组件和状态添加类型注解,确保类型安全。
    • 定义接口和类型,提升代码可维护性。
  5. 可访问性(a11y)
    • 为动态内容添加 ARIA 属性。
    • 提供键盘导航支持。
  6. 手机端适配
    • 响应式布局,适配不同屏幕尺寸。
    • 优化触控交互(如点击、滑动)。
  7. 部署
    • 集成到 Vite 项目,部署到 Vercel。
    • 支持 CDN 加速静态资源加载。

需求背后的意义

这些需求覆盖了 React 渲染流程的核心场景,同时为学习性能优化提供了实践机会:

  • 动态仪表盘:模拟真实业务场景,展示渲染复杂性。
  • 初次渲染:优化首屏加载速度,提升用户体验。
  • 重渲染优化:减少性能开销,适合大规模应用。
  • TypeScript 集成:提升代码质量,减少运行时错误。
  • 可访问性:满足无障碍标准,扩大用户覆盖。
  • 手机端适配:适配移动设备,提升用户体验。

技术栈选择

在实现动态仪表盘应用之前,我们需要选择合适的技术栈。以下是本项目使用的工具和技术,以及选择它们的理由:

  • React 18
    核心前端框架,支持 Fiber 架构和并发渲染,适合动态应用。
  • TypeScript
    提供类型安全,增强代码可维护性和 IDE 补全,适合复杂项目。
  • Vite
    构建工具,提供快速的开发服务器和高效的打包能力。
  • React Query
    数据获取和状态管理库,简化异步数据处理。
  • Framer Motion
    用于实现动画效果,提升用户体验。
  • Tailwind CSS
    提供灵活的样式解决方案,支持响应式设计。
  • Vercel
    用于部署应用,提供高可用性和全球 CDN 支持。

技术栈优势

  • React 18:支持并发渲染,优化复杂应用性能。
  • TypeScript:提升代码质量,减少运行时错误。
  • Vite:启动速度快,热更新体验优越。
  • React Query:简化数据获取和缓存,减少手动状态管理。
  • Framer Motion:实现流畅的动画效果。
  • Tailwind CSS:简化样式开发,支持响应式设计。
  • Vercel:与 React 生态深度集成,部署简单。

这些工具组合不仅易于上手,还能帮助开发者掌握 React 渲染的最新实践。


项目实现

现在进入核心部分——代码实现。我们将从项目搭建开始,逐步实现动态仪表盘的初次渲染、重渲染优化、TypeScript 集成、可访问性和部署。

1. 项目搭建

使用 Vite 创建一个 React + TypeScript 项目:

npm create vite@latest dashboard -- --template react-ts
cd dashboard
npm install
npm run dev

安装必要的依赖:

npm install @tanstack/react-query framer-motion tailwindcss postcss autoprefixer chart.js react-chartjs-2

初始化 Tailwind CSS:

npx tailwindcss init -p

编辑 tailwind.config.js

/** @type {import('tailwindcss').Config} */
export default {content: ["./index.html","./src/**/*.{js,ts,jsx,tsx}",],theme: {extend: {},},plugins: [],
}

src/index.css 中引入 Tailwind:

@tailwind base;
@tailwind components;
@tailwind utilities;

2. 组件拆分

我们将应用拆分为以下组件:

  • App:根组件,负责整体布局。
  • Dashboard:展示指标卡和图表。
  • FilterBar:处理数据过滤和排序。
  • ThemeToggle:切换主题,触发重渲染。
  • ChartComponent:渲染动态图表。
  • AccessibilityPanel:管理可访问性设置。
文件结构
src/
├── components/
│   ├── Dashboard.tsx
│   ├── FilterBar.tsx
│   ├── ThemeToggle.tsx
│   ├── ChartComponent.tsx
│   └── AccessibilityPanel.tsx
├── hooks/
│   └── useDashboardData.ts
├── types/
│   └── index.ts
├── App.tsx
├── main.tsx
└── index.css

3. 初次渲染流程

React 的初次渲染将 JSX 转换为真实 DOM,涉及以下步骤:

  1. JSX 解析:Babel 将 JSX 编译为 React.createElement 调用。
  2. 虚拟 DOM 创建:生成虚拟 DOM 树(React Element)。
  3. Fiber 架构:将虚拟 DOM 转换为 Fiber 节点树。
  4. 协调(Reconciliation):遍历 Fiber 树,生成真实 DOM。
  5. 渲染:将 DOM 挂载到页面。
3.1 JSX 解析与虚拟 DOM

src/App.tsx

import Dashboard from './components/Dashboard';function App() {return (<div className="min-h-screen bg-gray-100"><h1 className="text-3xl font-bold text-center p-4">动态仪表盘</h1><Dashboard /></div>);
}export default App;

解析过程

  • JSX 编译为:
    React.createElement('div', { className: 'min-h-screen bg-gray-100' }, [React.createElement('h1', { className: 'text-3xl font-bold text-center p-4' }, '动态仪表盘'),React.createElement(Dashboard, null)
    ]);
    
  • 生成虚拟 DOM 树,表示组件结构。
3.2 Fiber 架构

React 18 使用 Fiber 架构,将渲染任务分解为可中断的单元:

  • Fiber 节点:每个组件或元素对应一个 Fiber 节点,包含状态、Props 和 DOM 引用。
  • 工作循环:React 调度器(Scheduler)按优先级执行 Fiber 任务。
  • 协调阶段:比较虚拟 DOM,标记需要更新的节点。
  • 提交阶段:将更新应用到真实 DOM。

src/components/Dashboard.tsx

import { useState } from 'react';
import FilterBar from './FilterBar';
import ChartComponent from './ChartComponent';
import ThemeToggle from './ThemeToggle';function Dashboard() {const [theme, setTheme] = useState('light');return (<div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-5xl mx-auto p-4"><FilterBar /><ChartComponent /><ThemeToggle theme={theme} setTheme={setTheme} /></div>);
}export default Dashboard;

Fiber 过程

  • React 创建 Fiber 节点树,包含 divFilterBarChartComponentThemeToggle
  • 调度器分配优先级,优先渲染高优先级任务(如用户交互)。
  • 协调阶段生成真实 DOM 结构。

避坑

  • 避免在初次渲染中执行复杂计算,推迟到 useEffect
  • 使用 React.StrictMode 检查潜在问题。

4. 重渲染机制

重渲染由状态或 Props 变化触发,涉及以下步骤:

  1. 触发更新useStateuseReducer 的更新函数调用。
  2. 协调:比较新旧虚拟 DOM,执行 Diff 算法。
  3. 提交:更新真实 DOM,仅应用变化部分。
4.1 触发重渲染

src/components/ThemeToggle.tsx

import { HTMLAttributes } from 'react';interface ThemeToggleProps extends HTMLAttributes<HTMLDivElement> {theme: string;setTheme: (theme: string) => void;
}function ThemeToggle({ theme, setTheme, className, ...props }: ThemeToggleProps) {const toggleTheme = () => {setTheme(theme === 'light' ? 'dark' : 'light');};return (<div className={`p-4 bg-white rounded-lg shadow ${className}`} {...props}><buttononClick={toggleTheme}className="px-4 py-2 bg-blue-500 text-white rounded-lg"aria-label={`切换到${theme === 'light' ? '暗黑模式' : '亮色模式'}`}>{theme === 'light' ? '暗黑模式' : '亮色模式'}</button></div>);
}export default ThemeToggle;

触发过程

  • setTheme 调用触发组件重渲染。
  • React 更新 Fiber 节点树,标记变化的节点。
  • Diff 算法比较新旧虚拟 DOM,仅更新 theme 相关的 DOM。
4.2 Diff 算法

React 的 Diff 算法优化重渲染:

  • 同层比较:只比较同一层级的节点。
  • Key 优化:使用 key 属性加速列表渲染。
  • 类型检查:不同类型的组件直接替换。

src/components/ChartComponent.tsx

import { useMemo } from 'react';
import { Line } from 'react-chartjs-2';
import {Chart as ChartJS,CategoryScale,LinearScale,PointElement,LineElement,Title,Tooltip,Legend,
} from 'chart.js';ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);interface ChartData {labels: string[];datasets: {label: string;data: number[];borderColor: string;}[];
}function ChartComponent() {const data = useMemo<ChartData>(() => ({labels: ['1月', '2月', '3月', '4月', '5月'],datasets: [{label: '销售数据',data: [65, 59, 80, 81, 56],borderColor: '#3b82f6',},],}),[]);return (<div className="p-4 bg-white rounded-lg shadow"><h2 className="text-xl font-bold mb-4">销售趋势</h2><Line data={data} aria-label="销售趋势图表" /></div>);
}export default ChartComponent;

Diff 优化

  • 使用 useMemo 缓存 data,避免重复计算。
  • 确保 key 属性唯一,优化列表渲染。

避坑

  • 避免频繁更改组件引用,导致 Diff 失效。
  • 使用 React.memo 包裹纯组件。

5. 性能优化

5.1 使用 useMemouseCallback

src/components/FilterBar.tsx

import { useState, useCallback } from 'react';function FilterBar() {const [filter, setFilter] = useState('all');const handleFilter = useCallback((value: string) => {setFilter(value);}, []);return (<div className="p-4 bg-white rounded-lg shadow"><h2 className="text-xl font-bold mb-4">过滤器</h2><selectvalue={filter}onChange={(e) => handleFilter(e.target.value)}className="p-2 border rounded-lg"aria-label="过滤数据"><option value="all">全部</option><option value="high">高值</option><option value="low">低值</option></select></div>);
}export default FilterBar;

优点

  • useCallback 缓存 handleFilter,避免子组件重渲染。
  • useMemo 缓存复杂计算,减少性能开销。

避坑

  • 仅对必要函数和数据使用 useMemo/useCallback
  • 避免过度优化,导致代码复杂。
5.2 使用 React.memo

src/components/ChartComponent.tsx(更新):

import { memo } from 'react';const ChartComponent = memo(function ChartComponent() {const data = useMemo<ChartData>(() => ({labels: ['1月', '2月', '3月', '4月', '5月'],datasets: [{label: '销售数据',data: [65, 59, 80, 81, 56],borderColor: '#3b82f6',},],}),[]);return (<div className="p-4 bg-white rounded-lg shadow"><h2 className="text-xl font-bold mb-4">销售趋势</h2><Line data={data} aria-label="销售趋势图表" /></div>);
});export default ChartComponent;

优点

  • React.memo 防止 Props 未变化时的重渲染。
  • 适合纯组件或计算开销大的组件。

避坑

  • 确保 Props 是简单类型或使用 useMemo 包装。
  • 测试 React.memo 的效果,避免无效优化。
5.3 并发渲染

React 18 的并发渲染支持任务分片和优先级调度:

src/components/Dashboard.tsx(更新):

import { useTransition } from 'react';function Dashboard() {const [theme, setTheme] = useState('light');const [isPending, startTransition] = useTransition();const handleThemeChange = () => {startTransition(() => {setTheme(theme === 'light' ? 'dark' : 'light');});};return (<div className={`grid grid-cols-1 md:grid-cols-2 gap-4 max-w-5xl mx-auto p-4 ${isPending ? 'opacity-50' : ''}`}><FilterBar /><ChartComponent /><ThemeToggle theme={theme} setTheme={handleThemeChange} /></div>);
}

优点

  • useTransition 将低优先级更新推迟,优先渲染用户交互。
  • 提升复杂应用的流畅性。

避坑

  • 测试 isPending 状态,确保过渡效果自然。
  • 避免在高频更新中使用 useTransition

6. TypeScript 集成

src/types/index.ts

export interface ChartData {labels: string[];datasets: {label: string;data: number[];borderColor: string;}[];
}

src/components/ChartComponent.tsx(更新):

import { memo } from 'react';
import { Line } from 'react-chartjs-2';
import type { ChartData } from '../types';const ChartComponent = memo(function ChartComponent() {const data = useMemo<ChartData>(() => ({labels: ['1月', '2月', '3月', '4月', '5月'],datasets: [{label: '销售数据',data: [65, 59, 80, 81, 56],borderColor: '#3b82f6',},],}),[]);return (<div className="p-4 bg-white rounded-lg shadow"><h2 className="text-xl font-bold mb-4">销售趋势</h2><Line data={data} aria-label="销售趋势图表" /></div>);
});

避坑

  • 定义明确的类型接口,避免 any
  • useState 和 Props 添加类型注解。

7. 可访问性(a11y)

src/components/AccessibilityPanel.tsx

import { useState } from 'react';function AccessibilityPanel() {const [highContrast, setHighContrast] = useState(false);return (<div className="p-4 bg-white rounded-lg shadow"><h2 className="text-xl font-bold mb-4">可访问性设置</h2><label className="flex items-center space-x-2"><inputtype="checkbox"checked={highContrast}onChange={() => setHighContrast(!highContrast)}className="p-2"aria-label="启用高对比度模式"/><span>高对比度模式</span></label><div className={highContrast ? 'bg-black text-white' : ''}><p aria-live="polite">测试文本:{highContrast ? '高对比度' : '正常'}</p></div></div>);
}export default AccessibilityPanel;

避坑

  • 为动态内容添加 aria-livearia-label
  • 测试屏幕阅读器(如 NVDA、VoiceOver)。

8. 手机端适配

src/App.tsx(更新):

import Dashboard from './components/Dashboard';
import AccessibilityPanel from './components/AccessibilityPanel';function App() {return (<div className="min-h-screen bg-gray-100 p-2 md:p-4"><h1 className="text-2xl md:text-3xl font-bold text-center p-4">动态仪表盘</h1><div className="max-w-5xl mx-auto"><Dashboard /><AccessibilityPanel /></div></div>);
}

避坑

  • 测试不同屏幕尺寸的布局效果。
  • 确保触控区域足够大(至少 48x48 像素)。

9. 数据获取与 React Query

src/hooks/useDashboardData.ts

import { useQuery } from '@tanstack/react-query';
import type { ChartData } from '../types';export function useDashboardData() {return useQuery({queryKey: ['dashboard'],queryFn: async (): Promise<ChartData> => {// 模拟 API 调用await new Promise(resolve => setTimeout(resolve, 1000));return {labels: ['1月', '2月', '3月', '4月', '5月'],datasets: [{label: '销售数据',data: [65, 59, 80, 81, 56],borderColor: '#3b82f6',},],};},});
}

src/components/ChartComponent.tsx(更新):

import { memo } from 'react';
import { Line } from 'react-chartjs-2';
import { useDashboardData } from '../hooks/useDashboardData';const ChartComponent = memo(function ChartComponent() {const { data, isLoading } = useDashboardData();if (isLoading) {return <div className="p-4">加载中...</div>;}return (<div className="p-4 bg-white rounded-lg shadow"><h2 className="text-xl font-bold mb-4">销售趋势</h2><Line data={data!} aria-label="销售趋势图表" /></div>);
});

避坑

  • 处理加载和错误状态。
  • 使用 staleTime 优化缓存。

10. 部署

10.1 构建项目
npm run build
10.2 部署到 Vercel
  1. 注册 Vercel:访问 Vercel 官网并创建账号。
  2. 新建项目:选择“New Project”。
  3. 导入仓库:将项目推送至 GitHub 并导入。
  4. 配置构建
    • 构建命令:npm run build
    • 输出目录:dist
  5. 部署:点击“Deploy”.

避坑

  • 确保静态资源路径正确(使用相对路径)。
  • 使用 CDN 加速 Tailwind CSS 和 Chart.js。

常见问题与解决方案

11.1 不必要的重渲染

问题:父组件更新导致子组件重复渲染。

解决方案

  • 使用 React.memo 包裹子组件。
  • 使用 useMemo 缓存 Props:
    const memoizedData = useMemo(() => ({ key: value }), [value]);
    

11.2 状态管理复杂性

问题:多组件共享状态导致代码混乱。

解决方案

  • 使用 React Query 或 Context API:
    const AppContext = createContext({});
    

11.3 Fiber 调度问题

问题:高优先级任务阻塞低优先级任务。

解决方案

  • 使用 useTransition 分离优先级:
    startTransition(() => setState(newValue));
    

练习:添加动态过滤器

为巩固所学,设计一个练习:为仪表盘添加动态数据过滤器。

需求

  • 支持按时间范围过滤图表数据。
  • 动态更新图表,优化重渲染。
  • 使用 useMemo 和 React Query。

实现步骤

src/components/FilterBar.tsx(更新):

import { useState, useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';function FilterBar() {const [range, setRange] = useState('all');const queryClient = useQueryClient();const handleFilter = useCallback((value: string) => {setRange(value);queryClient.invalidateQueries({ queryKey: ['dashboard'] });}, [queryClient]);return (<div className="p-4 bg-white rounded-lg shadow"><h2 className="text-xl font-bold mb-4">时间范围</h2><selectvalue={range}onChange={(e) => handleFilter(e.target.value)}className="p-2 border rounded-lg"aria-label="选择时间范围"><option value="all">全部</option><option value="month">本月</option><option value="year">本年</option></select></div>);
}

目标

  • 学会结合 useMemo 和 React Query 优化动态数据渲染。

注意事项

  • 初次渲染:推迟复杂计算到 useEffect
  • 重渲染:使用 React.memouseMemo 减少开销。
  • TypeScript:定义清晰的类型接口。
  • 可访问性:为动态内容添加 ARIA 属性。
  • 学习建议:参考 React 18 文档、React Query 文档 和 Vite 文档.

结语

通过这个动态仪表盘项目,您深入了解了 React 的初次渲染和重渲染流程,掌握了 Fiber 架构、Diff 算法、并发渲染和优化策略。这些技能将帮助您构建高效、可维护的 React 应用,应对复杂业务场景。希望您继续探索 React 的高级功能,如服务器组件和复杂动画,打造卓越的用户体验!

http://www.dtcms.com/a/266898.html

相关文章:

  • 深入解析XFS文件系统:原理、工具与数据恢复实战
  • 【Go语言-Day 13】切片操作终极指南:append、copy与内存陷阱解析
  • 替代MT6701,3D 霍尔磁性角度传感器芯片
  • Go语言的协程池Ants
  • yolo性能评价指标(训练后生成文件解读)results、mAP、Precision、Recall、FPS、Confienc--笔记
  • 韩顺平之第九章综合练习-----------房屋出租管理系统
  • 从0写自己的操作系统(3)x86操作系统的中断和异常处理
  • 02每日简报20250704
  • Spring Boot + 本地部署大模型实现:安全性与可靠性保障
  • 高档宠物食品对宠物的健康益处有哪些?
  • MySQL/MariaDB数据库主从复制之基于二进制日志的方式
  • 如何查看自己电脑的显卡信息?
  • 力扣hot100题(1)
  • C++26 下一代C++标准
  • 通用人工智能三大方向系统梳理
  • 学习者的Python项目灵感
  • 【python实用小脚本-128】基于 Python 的 Hacker News 爬虫工具:自动化抓取新闻数据
  • [数据结构]详解红黑树
  • 小架构step系列04:springboot提供的依赖
  • mobaxterm终端sqlplus乱码问题解决
  • 使用循环抵消算法求解最小费用流问题
  • opencv的颜色通道问题 rgb bgr
  • 智绅科技:以科技为翼,构建养老安全守护网
  • Vue中对象赋值问题:对象引用被保留,仅部分属性被覆盖
  • 八股学习(三)---MySQL
  • 高流量发布会,保障支付系统稳定运行感想
  • Flink-05学习 接上节,将FlinkJedisPoolConfig 从Kafka写入Redis
  • 关于python
  • Javaweb - 10.2 Servlet
  • 【51单片机倒计时选位最右侧2位显示秒钟后最左侧1位显示8两秒后复位初始状态2个外部中断组合按键功能】2022-7-5