[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"post-java-21-零基础入门教程":3,"sidebar-data":25,"comments-POST-a4e1d5e8-65a8-4e34-bafc-415f013cc83e-1":364},{"id":4,"title":5,"slug":6,"content":7,"excerpt":5,"coverImage":8,"status":9,"isPinned":10,"pinnedAt":11,"viewCount":12,"publishedAt":13,"createdAt":13,"updatedAt":14,"category":15,"author":18,"tags":21},"a4e1d5e8-65a8-4e34-bafc-415f013cc83e","Java 21 零基础入门教程","java-21-零基础入门教程","# Java 21 零基础入门教程\n\n> 本文档提供完整的 Java 21 入门教程，从环境搭建到面向对象编程，包含最新的 Java 21 特性。\n\n## 目录\n\n- [第一部分：Java 简介与环境搭建](#第一部分java-简介与环境搭建)\n- [第二部分：第一个 Java 程序](#第二部分第一个-java-程序)\n- [第三部分：Java 基础语法](#第三部分java-基础语法)\n- [第四部分：流程控制](#第四部分流程控制)\n- [第五部分：数组](#第五部分数组)\n- [第六部分：方法（函数）](#第六部分方法函数)\n- [第七部分：面向对象编程](#第七部分面向对象编程)\n- [第八部分：Java 21 新特性](#第八部分java-21-新特性)\n- [第九部分：异常处理](#第九部分异常处理)\n- [第十部分：集合框架](#第十部分集合框架)\n- [学习建议](#学习建议)\n- [下一步学习方向](#下一步学习方向)\n\n## 第一部分：Java 简介与环境搭建\n\n### 1.1 什么是 Java？\n\nJava 是一种广泛使用的编程语言，具有以下特点：\n\n- **跨平台**：一次编写，到处运行\n- **面向对象**：支持封装、继承、多态\n- **简单易学**：语法类似 C++，但更简洁\n- **开源免费**：拥有强大的社区支持\n\n### 1.2 安装 Java 开发环境\n\n#### 下载 JDK 21\n\n1. 访问 [Oracle 官网](https:\u002F\u002Fwww.oracle.com\u002Fjava\u002Ftechnologies\u002Fjavase-jdk21-downloads.html)\n2. 选择对应操作系统的 JDK 21\n3. 下载并安装\n\n#### 配置环境变量\n\n**Windows**：\n\n```bash\n# 添加系统变量\nJAVA_HOME = C:\\Program Files\\Java\\jdk-21\nPATH = %JAVA_HOME%\\bin;...\n```\n\n**Mac\u002FLinux**：\n\n```bash\n# 编辑 ~\u002F.bashrc 或 ~\u002F.zshrc\nexport JAVA_HOME=\u002FLibrary\u002FJava\u002FJavaVirtualMachines\u002Fjdk-21.jdk\u002FContents\u002FHome\nexport PATH=$JAVA_HOME\u002Fbin:$PATH\n```\n\n#### 验证安装\n\n打开终端\u002F命令提示符，输入：\n\n```bash\njava -version\njavac -version\n```\n\n## 第二部分：第一个 Java 程序\n\n### 2.1 Hello World 程序\n\n创建文件 `HelloWorld.java`：\n\n```java\npublic class HelloWorld {\n    public static void main(String[] args) {\n        System.out.println(\\\"Hello, World!\\\");\n    }\n}\n```\n\n编译和运行：\n\n```bash\njavac HelloWorld.java    # 编译\njava HelloWorld          # 运行\n```\n\n### 2.2 程序结构解析\n\n- `public class HelloWorld`：定义类，类名必须与文件名相同\n- `public static void main(String[] args)`：程序入口点\n- `System.out.println()`：输出语句\n\n## 第三部分：Java 基础语法\n\n### 3.1 变量和数据类型\n\n```java\npublic class Variables {\n    public static void main(String[] args) {\n        \u002F\u002F 基本数据类型\n        int age = 25;                    \u002F\u002F 整数\n        double price = 19.99;            \u002F\u002F 浮点数\n        char grade = 'A';                \u002F\u002F 字符\n        boolean isJavaFun = true;        \u002F\u002F 布尔值\n        String name = \\\"张三\\\";            \u002F\u002F 字符串\n        \n        \u002F\u002F 输出变量\n        System.out.println(\\\"姓名: \\\" + name);\n        System.out.println(\\\"年龄: \\\" + age);\n        System.out.println(\\\"价格: \\\" + price);\n    }\n}\n```\n\n### 3.2 数据类型详解\n\n| 类型 | 大小 | 范围 | 示例 |\n|------|------|------|------|\n| byte | 8位 | -128 ~ 127 | `byte b = 100;` |\n| short | 16位 | -32768 ~ 32767 | `short s = 1000;` |\n| int | 32位 | -2³¹ ~ 2³¹-1 | `int i = 100000;` |\n| long | 64位 | -2⁶³ ~ 2⁶³-1 | `long l = 100000L;` |\n| float | 32位 | 单精度浮点数 | `float f = 3.14f;` |\n| double | 64位 | 双精度浮点数 | `double d = 3.14;` |\n| char | 16位 | Unicode字符 | `char c = 'A';` |\n| boolean | 1位 | true\u002Ffalse | `boolean flag = true;` |\n\n### 3.3 运算符\n\n```java\npublic class Operators {\n    public static void main(String[] args) {\n        int a = 10, b = 3;\n        \n        \u002F\u002F 算术运算符\n        System.out.println(\\\"a + b = \\\" + (a + b));  \u002F\u002F 13\n        System.out.println(\\\"a - b = \\\" + (a - b));  \u002F\u002F 7\n        System.out.println(\\\"a * b = \\\" + (a * b));  \u002F\u002F 30\n        System.out.println(\\\"a \u002F b = \\\" + (a \u002F b));  \u002F\u002F 3\n        System.out.println(\\\"a % b = \\\" + (a % b));  \u002F\u002F 1\n        \n        \u002F\u002F 比较运算符\n        System.out.println(\\\"a == b: \\\" + (a == b)); \u002F\u002F false\n        System.out.println(\\\"a > b: \\\" + (a > b));   \u002F\u002F true\n        \n        \u002F\u002F 逻辑运算符\n        boolean x = true, y = false;\n        System.out.println(\\\"x && y: \\\" + (x && y)); \u002F\u002F false\n        System.out.println(\\\"x || y: \\\" + (x || y)); \u002F\u002F true\n        System.out.println(\\\"!x: \\\" + (!x));         \u002F\u002F false\n    }\n}\n```\n\n## 第四部分：流程控制\n\n### 4.1 条件语句\n\n```java\npublic class Conditionals {\n    public static void main(String[] args) {\n        int score = 85;\n        \n        \u002F\u002F if-else 语句\n        if (score >= 90) {\n            System.out.println(\\\"优秀\\\");\n        } else if (score >= 80) {\n            System.out.println(\\\"良好\\\");\n        } else if (score >= 60) {\n            System.out.println(\\\"及格\\\");\n        } else {\n            System.out.println(\\\"不及格\\\");\n        }\n        \n        \u002F\u002F switch 语句 (Java 14+ 新语法)\n        String grade = switch (score \u002F 10) {\n            case 10, 9 -> \\\"A\\\";\n            case 8 -> \\\"B\\\";\n            case 7 -> \\\"C\\\";\n            case 6 -> \\\"D\\\";\n            default -> \\\"F\\\";\n        };\n        System.out.println(\\\"等级: \\\" + grade);\n    }\n}\n```\n\n### 4.2 循环语句\n\n```java\npublic class Loops {\n    public static void main(String[] args) {\n        \u002F\u002F for 循环\n        System.out.println(\\\"for 循环:\\\");\n        for (int i = 1; i \u003C= 5; i++) {\n            System.out.println(\\\"数字: \\\" + i);\n        }\n        \n        \u002F\u002F while 循环\n        System.out.println(\\\"\\\nwhile 循环:\\\");\n        int j = 1;\n        while (j \u003C= 3) {\n            System.out.println(\\\"计数: \\\" + j);\n            j++;\n        }\n        \n        \u002F\u002F do-while 循环\n        System.out.println(\\\"\\\ndo-while 循环:\\\");\n        int k = 1;\n        do {\n            System.out.println(\\\"值: \\\" + k);\n            k++;\n        } while (k \u003C= 3);\n        \n        \u002F\u002F 增强 for 循环\n        System.out.println(\\\"\\\n增强 for 循环:\\\");\n        int[] numbers = {1, 2, 3, 4, 5};\n        for (int num : numbers) {\n            System.out.println(\\\"数组元素: \\\" + num);\n        }\n    }\n}\n```\n\n## 第五部分：数组\n\n### 5.1 数组的基本操作\n\n```java\npublic class ArraysDemo {\n    public static void main(String[] args) {\n        \u002F\u002F 数组声明和初始化\n        int[] numbers = new int[5];           \u002F\u002F 长度为5的数组\n        int[] scores = {90, 85, 78, 92, 88}; \u002F\u002F 直接初始化\n        \n        \u002F\u002F 访问和修改数组元素\n        numbers[0] = 10;\n        numbers[1] = 20;\n        \n        System.out.println(\\\"第一个分数: \\\" + scores[0]);\n        System.out.println(\\\"数组长度: \\\" + scores.length);\n        \n        \u002F\u002F 遍历数组\n        System.out.println(\\\"所有分数:\\\");\n        for (int i = 0; i \u003C scores.length; i++) {\n            System.out.println(\\\"分数 \\\" + i + \\\": \\\" + scores[i]);\n        }\n        \n        \u002F\u002F 使用增强 for 循环\n        System.out.println(\\\"使用增强 for 循环:\\\");\n        for (int score : scores) {\n            System.out.println(score);\n        }\n    }\n}\n```\n\n### 5.2 多维数组\n\n```java\npublic class MultiDimArray {\n    public static void main(String[] args) {\n        \u002F\u002F 二维数组\n        int[][] matrix = {\n            {1, 2, 3},\n            {4, 5, 6},\n            {7, 8, 9}\n        };\n        \n        \u002F\u002F 遍历二维数组\n        for (int i = 0; i \u003C matrix.length; i++) {\n            for (int j = 0; j \u003C matrix[i].length; j++) {\n                System.out.print(matrix[i][j] + \\\" \\\");\n            }\n            System.out.println();\n        }\n    }\n}\n```\n\n## 第六部分：方法（函数）\n\n### 6.1 方法的定义和使用\n\n```java\npublic class Methods {\n    \n    \u002F\u002F 无返回值的方法\n    public static void greet(String name) {\n        System.out.println(\\\"你好, \\\" + name + \\\"!\\\");\n    }\n    \n    \u002F\u002F 有返回值的方法\n    public static int add(int a, int b) {\n        return a + b;\n    }\n    \n    \u002F\u002F 方法重载\n    public static double add(double a, double b) {\n        return a + b;\n    }\n    \n    public static void main(String[] args) {\n        \u002F\u002F 调用方法\n        greet(\\\"世界\\\");\n        \n        int sum1 = add(5, 3);\n        double sum2 = add(2.5, 3.7);\n        \n        System.out.println(\\\"整数和: \\\" + sum1);\n        System.out.println(\\\"浮点数和: \\\" + sum2);\n        \n        \u002F\u002F 递归方法示例\n        int factorial = factorial(5);\n        System.out.println(\\\"5的阶乘: \\\" + factorial);\n    }\n    \n    \u002F\u002F 递归方法\n    public static int factorial(int n) {\n        if (n == 0 || n == 1) {\n            return 1;\n        }\n        return n * factorial(n - 1);\n    }\n}\n```\n\n## 第七部分：面向对象编程\n\n### 7.1 类和对象\n\n```java\n\u002F\u002F 定义类\nclass Student {\n    \u002F\u002F 属性（字段）\n    String name;\n    int age;\n    String studentId;\n    \n    \u002F\u002F 构造方法\n    public Student(String name, int age, String studentId) {\n        this.name = name;\n        this.age = age;\n        this.studentId = studentId;\n    }\n    \n    \u002F\u002F 方法\n    public void study() {\n        System.out.println(name + \\\"正在学习...\\\");\n    }\n    \n    public void displayInfo() {\n        System.out.println(\\\"学生信息:\\\");\n        System.out.println(\\\"姓名: \\\" + name);\n        System.out.println(\\\"年龄: \\\" + age);\n        System.out.println(\\\"学号: \\\" + studentId);\n    }\n}\n\npublic class OOPDemo {\n    public static void main(String[] args) {\n        \u002F\u002F 创建对象\n        Student student1 = new Student(\\\"张三\\\", 20, \\\"2023001\\\");\n        Student student2 = new Student(\\\"李四\\\", 19, \\\"2023002\\\");\n        \n        \u002F\u002F 使用对象\n        student1.displayInfo();\n        student1.study();\n        \n        student2.displayInfo();\n        student2.study();\n    }\n}\n```\n\n### 7.2 封装\n\n```java\nclass BankAccount {\n    \u002F\u002F 私有字段（封装）\n    private String accountNumber;\n    private double balance;\n    private String owner;\n    \n    \u002F\u002F 构造方法\n    public BankAccount(String accountNumber, String owner, double initialBalance) {\n        this.accountNumber = accountNumber;\n        this.owner = owner;\n        this.balance = initialBalance;\n    }\n    \n    \u002F\u002F Getter 方法\n    public String getAccountNumber() {\n        return accountNumber;\n    }\n    \n    public double getBalance() {\n        return balance;\n    }\n    \n    public String getOwner() {\n        return owner;\n    }\n    \n    \u002F\u002F 存款方法\n    public void deposit(double amount) {\n        if (amount > 0) {\n            balance += amount;\n            System.out.println(\\\"存款成功! 当前余额: \\\" + balance);\n        } else {\n            System.out.println(\\\"存款金额必须大于0\\\");\n        }\n    }\n    \n    \u002F\u002F 取款方法\n    public void withdraw(double amount) {\n        if (amount > 0 && amount \u003C= balance) {\n            balance -= amount;\n            System.out.println(\\\"取款成功! 当前余额: \\\" + balance);\n        } else {\n            System.out.println(\\\"取款失败: 余额不足或金额无效\\\");\n        }\n    }\n}\n\npublic class EncapsulationDemo {\n    public static void main(String[] args) {\n        BankAccount account = new BankAccount(\\\"123456\\\", \\\"张三\\\", 1000);\n        \n        account.deposit(500);\n        account.withdraw(200);\n        account.withdraw(2000); \u002F\u002F 应该失败\n        \n        System.out.println(\\\"最终余额: \\\" + account.getBalance());\n    }\n}\n```\n\n### 7.3 继承\n\n```java\n\u002F\u002F 父类\nclass Animal {\n    String name;\n    int age;\n    \n    public Animal(String name, int age) {\n        this.name = name;\n        this.age = age;\n    }\n    \n    public void eat() {\n        System.out.println(name + \\\"正在吃东西\\\");\n    }\n    \n    public void sleep() {\n        System.out.println(name + \\\"正在睡觉\\\");\n    }\n}\n\n\u002F\u002F 子类\nclass Dog extends Animal {\n    String breed;\n    \n    public Dog(String name, int age, String breed) {\n        super(name, age); \u002F\u002F 调用父类构造方法\n        this.breed = breed;\n    }\n    \n    \u002F\u002F 方法重写\n    @Override\n    public void eat() {\n        System.out.println(name + \\\"正在吃狗粮\\\");\n    }\n    \n    \u002F\u002F 子类特有方法\n    public void bark() {\n        System.out.println(name + \\\"在汪汪叫\\\");\n    }\n}\n\npublic class InheritanceDemo {\n    public static void main(String[] args) {\n        Dog dog = new Dog(\\\"旺财\\\", 3, \\\"金毛\\\");\n        dog.eat();      \u002F\u002F 调用重写的方法\n        dog.sleep();    \u002F\u002F 调用继承的方法\n        dog.bark();     \u002F\u002F 调用子类特有方法\n    }\n}\n```\n\n### 7.4 多态\n\n```java\nclass Shape {\n    public void draw() {\n        System.out.println(\\\"绘制形状\\\");\n    }\n    \n    public double calculateArea() {\n        return 0;\n    }\n}\n\nclass Circle extends Shape {\n    double radius;\n    \n    public Circle(double radius) {\n        this.radius = radius;\n    }\n    \n    @Override\n    public void draw() {\n        System.out.println(\\\"绘制圆形，半径: \\\" + radius);\n    }\n    \n    @Override\n    public double calculateArea() {\n        return Math.PI * radius * radius;\n    }\n}\n\nclass Rectangle extends Shape {\n    double width, height;\n    \n    public Rectangle(double width, double height) {\n        this.width = width;\n        this.height = height;\n    }\n    \n    @Override\n    public void draw() {\n        System.out.println(\\\"绘制矩形，宽: \\\" + width + \\\", 高: \\\" + height);\n    }\n    \n    @Override\n    public double calculateArea() {\n        return width * height;\n    }\n}\n\npublic class PolymorphismDemo {\n    public static void main(String[] args) {\n        \u002F\u002F 多态示例\n        Shape[] shapes = new Shape[2];\n        shapes[0] = new Circle(5);\n        shapes[1] = new Rectangle(4, 6);\n        \n        for (Shape shape : shapes) {\n            shape.draw();\n            System.out.println(\\\"面积: \\\" + shape.calculateArea());\n            System.out.println(\\\"---------\\\");\n        }\n    }\n}\n```\n\n## 第八部分：Java 21 新特性\n\n### 8.1 记录类 (Records)\n\n```java\n\u002F\u002F 传统方式\nclass Person {\n    private final String name;\n    private final int age;\n    \n    public Person(String name, int age) {\n        this.name = name;\n        this.age = age;\n    }\n    \n    public String name() { return name; }\n    public int age() { return age; }\n    \n    @Override\n    public boolean equals(Object o) {\n        \u002F\u002F 复杂的equals实现...\n    }\n    \n    @Override\n    public int hashCode() {\n        \u002F\u002F 复杂的hashCode实现...\n    }\n    \n    @Override\n    public String toString() {\n        \u002F\u002F toString实现...\n    }\n}\n\n\u002F\u002F Java 16+ 记录类 (简化版)\nrecord PersonRecord(String name, int age) {\n    \u002F\u002F 自动生成构造方法、getter、equals、hashCode、toString\n}\n\npublic class RecordsDemo {\n    public static void main(String[] args) {\n        PersonRecord person = new PersonRecord(\\\"张三\\\", 25);\n        System.out.println(person.name());    \u002F\u002F 自动生成的getter\n        System.out.println(person.age());     \u002F\u002F 自动生成的getter\n        System.out.println(person);           \u002F\u002F 自动生成的toString\n    }\n}\n```\n\n### 8.2 文本块 (Text Blocks)\n\n```java\npublic class TextBlocks {\n    public static void main(String[] args) {\n        \u002F\u002F 传统字符串\n        String oldStyle = \\\"{\\\n\\\" +\n                         \\\"  \\\\\"name\\\\\": \\\\\"张三\\\\\",\\\n\\\" +\n                         \\\"  \\\\\"age\\\\\": 25\\\n\\\" +\n                         \\\"}\\\";\n        \n        \u002F\u002F Java 15+ 文本块\n        String json = \\\"\\\"\\\"\n                      {\n                        \\\"name\\\": \\\"张三\\\",\n                        \\\"age\\\": 25\n                      }\n                      \\\"\\\"\\\";\n        \n        String html = \\\"\\\"\\\"\n                      \u003Chtml>\n                          \u003Cbody>\n                              \u003Ch1>欢迎\u003C\u002Fh1>\n                          \u003C\u002Fbody>\n                      \u003C\u002Fhtml>\n                      \\\"\\\"\\\";\n        \n        System.out.println(json);\n        System.out.println(html);\n    }\n}\n```\n\n### 8.3 模式匹配\n\n```java\npublic class PatternMatching {\n    public static void main(String[] args) {\n        Object obj = \\\"Hello, Java 21!\\\";\n        \n        \u002F\u002F 传统的 instanceof\n        if (obj instanceof String) {\n            String str = (String) obj;\n            System.out.println(str.toUpperCase());\n        }\n        \n        \u002F\u002F Java 16+ 模式匹配 instanceof\n        if (obj instanceof String str) {\n            System.out.println(str.toUpperCase());\n        }\n        \n        \u002F\u002F switch 表达式 + 模式匹配\n        Object value = 42;\n        String result = switch (value) {\n            case Integer i -> \\\"整数: \\\" + i;\n            case String s -> \\\"字符串: \\\" + s;\n            case Double d -> \\\"浮点数: \\\" + d;\n            default -> \\\"未知类型\\\";\n        };\n        System.out.println(result);\n    }\n}\n```\n\n## 第九部分：异常处理\n\n```java\npublic class ExceptionHandling {\n    \n    public static int divide(int a, int b) {\n        return a \u002F b;\n    }\n    \n    public static void main(String[] args) {\n        \u002F\u002F 基本的异常处理\n        try {\n            int result = divide(10, 0);\n            System.out.println(\\\"结果: \\\" + result);\n        } catch (ArithmeticException e) {\n            System.out.println(\\\"发生算术异常: \\\" + e.getMessage());\n        } finally {\n            System.out.println(\\\"这是finally块，总是会执行\\\");\n        }\n        \n        \u002F\u002F 多个catch块\n        try {\n            int[] numbers = {1, 2, 3};\n            System.out.println(numbers[5]); \u002F\u002F 数组越界\n        } catch (ArrayIndexOutOfBoundsException e) {\n            System.out.println(\\\"数组索引越界: \\\" + e.getMessage());\n        } catch (Exception e) {\n            System.out.println(\\\"发生其他异常: \\\" + e.getMessage());\n        }\n        \n        \u002F\u002F 抛出异常\n        try {\n            checkAge(15);\n        } catch (IllegalArgumentException e) {\n            System.out.println(\\\"捕获异常: \\\" + e.getMessage());\n        }\n    }\n    \n    public static void checkAge(int age) {\n        if (age \u003C 18) {\n            throw new IllegalArgumentException(\\\"年龄必须大于等于18岁\\\");\n        }\n        System.out.println(\\\"年龄验证通过\\\");\n    }\n}\n```\n\n## 第十部分：集合框架\n\n```java\nimport java.util.*;\n\npublic class CollectionsDemo {\n    public static void main(String[] args) {\n        \u002F\u002F List - 有序集合\n        List\u003CString> list = new ArrayList\u003C>();\n        list.add(\\\"苹果\\\");\n        list.add(\\\"香蕉\\\");\n        list.add(\\\"橙子\\\");\n        System.out.println(\\\"List: \\\" + list);\n        \n        \u002F\u002F Set - 无序不重复集合\n        Set\u003CInteger> set = new HashSet\u003C>();\n        set.add(1);\n        set.add(2);\n        set.add(1); \u002F\u002F 重复元素，不会被添加\n        System.out.println(\\\"Set: \\\" + set);\n        \n        \u002F\u002F Map - 键值对\n        Map\u003CString, Integer> map = new HashMap\u003C>();\n        map.put(\\\"张三\\\", 25);\n        map.put(\\\"李四\\\", 30);\n        map.put(\\\"王五\\\", 28);\n        System.out.println(\\\"Map: \\\" + map);\n        System.out.println(\\\"张三的年龄: \\\" + map.get(\\\"张三\\\"));\n        \n        \u002F\u002F 遍历集合\n        System.out.println(\\\"\\\n遍历List:\\\");\n        for (String fruit : list) {\n            System.out.println(fruit);\n        }\n        \n        System.out.println(\\\"\\\n遍历Map:\\\");\n        for (Map.Entry\u003CString, Integer> entry : map.entrySet()) {\n            System.out.println(entry.getKey() + \\\": \\\" + entry.getValue());\n        }\n        \n        \u002F\u002F 使用 Stream API (Java 8+)\n        System.out.println(\\\"\\\n使用Stream:\\\");\n        list.stream()\n            .filter(fruit -> fruit.length() > 2)\n            .forEach(System.out::println);\n    }\n}\n```\n\n## 学习建议\n\n1. **多动手实践**：每个示例代码都要亲自编写和运行\n2. **理解概念**：不要死记硬背，要理解面向对象的思想\n3. **阅读文档**：学会查看 [Java 官方文档](https:\u002F\u002Fdocs.oracle.com\u002Fjavase\u002F)\n4. **做项目**：尝试编写小项目来巩固知识\n5. **参与社区**：加入编程社区，向他人学习和提问\n\n## 下一步学习方向\n\n- Java 高级特性（泛型、反射、注解）\n- Java 网络编程\n- 数据库连接（JDBC）\n- 多线程编程\n- Spring 框架\n- 微服务开发\n\n---\n\n**下载说明**：您可以将此文档保存为 `Java21-入门教程.md` 文件，方便随时查阅和学习。\n\n这个教程涵盖了 Java 21 的基础知识，适合零基础学习者。建议按照顺序学习每个部分，并完成相应的练习。祝你学习顺利！","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fab7fca936a4c2c439faaa731c55a8da4_1762759275356.png","PUBLISHED",false,null,897,"2025-11-10T07:21:19.000Z","2026-06-27T11:04:01.926Z",{"id":16,"name":17,"slug":17},"d10456a5-e649-4741-a38f-f07f266ce5f2","开发环境",{"id":19,"name":20,"avatar":11},"f9d0f2de-c700-4f90-b535-afd3dbe78128","Admin",[22],{"id":23,"name":24,"slug":24},"692d5d68-b188-4e5c-aca8-65d0229399a1","渐变",{"categories":26,"tags":113,"postCount":295,"tagCount":296,"hotPosts":297},[27,35,42,49,53,60,67,71,78,82,86,92,98,103,107],{"id":28,"name":29,"slug":30,"description":31,"sortOrder":32,"createdAt":33,"updatedAt":33,"postCount":34},"e8d0bd45-d10c-46d3-8afb-0c072df7f8a7","技术","tech","技术文章",0,"2026-06-27T04:18:37.371Z",1,{"id":36,"name":37,"slug":38,"description":37,"sortOrder":34,"createdAt":39,"updatedAt":40,"postCount":41},"15ac46ad-edf7-4435-9a64-ff78117d58c5","Vue3 生态","vue3-生态","2022-05-21T08:05:39.000Z","2023-02-08T02:49:14.000Z",6,{"id":43,"name":44,"slug":45,"description":44,"sortOrder":46,"createdAt":47,"updatedAt":40,"postCount":48},"11d4d397-685c-4180-a7b3-9b0e3a1e411e","Css","css",2,"2022-05-23T07:19:37.000Z",9,{"id":16,"name":17,"slug":17,"description":17,"sortOrder":50,"createdAt":51,"updatedAt":40,"postCount":52},3,"2022-05-24T01:52:41.000Z",13,{"id":54,"name":55,"slug":56,"description":55,"sortOrder":57,"createdAt":58,"updatedAt":40,"postCount":59},"5ed5cc62-43ea-49a2-b0b2-38bc7aae52a0","Vue3","vue3",4,"2022-05-24T01:55:05.000Z",8,{"id":61,"name":62,"slug":63,"description":62,"sortOrder":64,"createdAt":65,"updatedAt":40,"postCount":66},"da130ba9-d4f4-49f3-aa0f-149078097ef0","JavaScript","javascript",5,"2022-05-24T02:22:57.000Z",18,{"id":68,"name":69,"slug":69,"description":69,"sortOrder":41,"createdAt":70,"updatedAt":40,"postCount":34},"d8cbe380-54b3-4a61-a12d-5438c2918574","限时优惠","2022-05-25T07:18:03.000Z",{"id":72,"name":73,"slug":74,"description":73,"sortOrder":75,"createdAt":76,"updatedAt":40,"postCount":77},"e0f3b8d8-cfe7-41fb-802b-a79699d95968","JavaScript插件","javascript插件",7,"2022-06-01T14:08:31.000Z",16,{"id":79,"name":80,"slug":80,"description":80,"sortOrder":59,"createdAt":81,"updatedAt":40,"postCount":41},"4ea3d8af-9cc3-49bb-a9cd-34dbcdc3bd85","构建工具","2022-06-02T07:28:13.000Z",{"id":83,"name":84,"slug":84,"description":84,"sortOrder":48,"createdAt":85,"updatedAt":40,"postCount":59},"9ed9827c-9cbb-42da-80e4-d04c7fdba886","开发工具","2022-06-21T03:35:05.000Z",{"id":87,"name":88,"slug":89,"description":88,"sortOrder":90,"createdAt":91,"updatedAt":40,"postCount":64},"6b9179c3-17b2-43ff-a431-a03d6eb32d89","Vue2 生态","vue2-生态",10,"2022-07-16T13:14:29.000Z",{"id":93,"name":94,"slug":95,"description":94,"sortOrder":96,"createdAt":97,"updatedAt":40,"postCount":64},"73a5f62c-3c47-45b9-9ae2-f29953ae8dc0","Node","node",11,"2022-07-16T13:15:39.000Z",{"id":99,"name":100,"slug":100,"description":100,"sortOrder":101,"createdAt":102,"updatedAt":40,"postCount":46},"2b696c16-48ef-403b-a88b-6e57cfc79596","开发问题",12,"2022-07-16T14:06:54.000Z",{"id":104,"name":105,"slug":105,"description":105,"sortOrder":52,"createdAt":106,"updatedAt":40,"postCount":34},"c0f0561e-a47a-4ecd-8caa-cc1df2315d57","算法","2022-07-16T14:22:34.000Z",{"id":108,"name":109,"slug":110,"description":109,"sortOrder":111,"createdAt":112,"updatedAt":40,"postCount":50},"a629c1f7-29f1-439e-be3c-29670b17ba20","Vue2","vue2",15,"2022-07-16T14:41:51.000Z",[114,120,125,128,133,138,142,146,151,156,161,166,169,174,178,182,187,192,197,202,206,210,214,216,220,225,230,233,236,239,242,245,249,252,254,256,259,263,266,270,274,277,280,282,283,285,289,292],{"id":115,"name":116,"slug":117,"createdAt":118,"updatedAt":119},"076bd8b9-293e-45cb-9dc3-e162007ca474","Axios","axios","2022-06-05T07:41:56.000Z","2025-12-30T07:26:21.000Z",{"id":121,"name":122,"slug":123,"createdAt":124,"updatedAt":40},"2aa7f6d0-1fac-4ed1-b9bb-f3afc813f42c","Axure","axure","2022-06-21T03:35:15.000Z",{"id":126,"name":44,"slug":45,"createdAt":127,"updatedAt":40},"b084ddd8-09be-4e57-98f0-cf4e376aecd7","2022-05-21T09:59:55.000Z",{"id":129,"name":130,"slug":131,"createdAt":132,"updatedAt":40},"78a62bff-ff77-4878-8c25-3e6aae18c668","Docker","docker","2022-07-16T14:34:37.000Z",{"id":134,"name":135,"slug":136,"createdAt":137,"updatedAt":40},"2de16806-ef3f-4e54-a259-d1e1e182468c","Git","git","2022-07-16T14:25:15.000Z",{"id":139,"name":140,"slug":141,"createdAt":127,"updatedAt":40},"994cc226-578b-4a72-a57e-a47a63d2793e","JavaScript生态","javascript生态",{"id":143,"name":144,"slug":145,"createdAt":127,"updatedAt":40},"5086e93c-23b9-43d3-9643-cc87f0e9ee94","JenKins","jenkins",{"id":147,"name":148,"slug":149,"createdAt":150,"updatedAt":40},"b73007a8-bb5c-42a8-9fd9-163033a5b45d","Linux","linux","2022-07-16T14:40:17.000Z",{"id":152,"name":153,"slug":154,"createdAt":155,"updatedAt":40},"0b658b92-dd6b-4db3-a398-9f6d69950a02","Markdown","markdown","2022-07-16T14:39:25.000Z",{"id":157,"name":158,"slug":159,"createdAt":160,"updatedAt":40},"ab034d3a-6e5b-4db5-a2dc-faf4ccbb63f5","Nest","nest","2022-07-16T13:15:49.000Z",{"id":162,"name":163,"slug":164,"createdAt":165,"updatedAt":40},"52c41978-da06-4962-9636-45bbaeedda80","Nginx","nginx","2022-05-21T09:59:56.000Z",{"id":167,"name":168,"slug":168,"createdAt":165,"updatedAt":40},"0f1cc678-40e4-44b1-b2cf-a6fd8a1c867a","npm",{"id":170,"name":171,"slug":172,"createdAt":173,"updatedAt":40},"a4370d78-70e1-4073-a8f6-3dc5d81fd8fd","Nuxt","nuxt","2022-06-01T13:07:07.000Z",{"id":175,"name":176,"slug":177,"createdAt":127,"updatedAt":40},"d232e01f-048e-4151-8a0a-fff9561f946f","Pinia","pinia",{"id":179,"name":180,"slug":181,"createdAt":165,"updatedAt":40},"14e9ab02-b0bb-4c85-8604-fe6f1f0f33cd","Pnpm","pnpm",{"id":183,"name":184,"slug":185,"createdAt":186,"updatedAt":186},"399d1d38-cc0d-43ce-8baf-c769447a2ebd","React生态","react生态","2023-02-21T02:03:09.000Z",{"id":188,"name":189,"slug":190,"createdAt":191,"updatedAt":40},"c95bbe84-bdd0-410a-86a9-e87958c55f4f","Redis","redis","2022-10-05T05:14:14.000Z",{"id":193,"name":194,"slug":195,"createdAt":196,"updatedAt":40},"6d05f9df-e116-450f-af57-85ed710c4870","Swiper","swiper","2022-06-01T14:08:46.000Z",{"id":198,"name":199,"slug":200,"createdAt":201,"updatedAt":40},"66f3aeb0-84ef-45f6-a43a-944eefc9895a","Vite","vite","2022-06-02T07:28:24.000Z",{"id":203,"name":204,"slug":205,"createdAt":127,"updatedAt":40},"bf5b94d3-090b-4098-a03c-4bc69781fb2d","Vue","vue",{"id":207,"name":208,"slug":209,"createdAt":165,"updatedAt":40},"2f7fb1be-b9c5-4606-b54f-e9f66f2653b2","Vue-Router","vue-router",{"id":211,"name":212,"slug":213,"createdAt":127,"updatedAt":40},"2fef3b91-1c1c-4ae8-b2c1-0e04b4f9b3a2","Vue2生态","vue2生态",{"id":215,"name":55,"slug":56,"createdAt":127,"updatedAt":40},"62b94c93-724f-488d-8fc5-0449971d9204",{"id":217,"name":218,"slug":219,"createdAt":127,"updatedAt":40},"20bff9cd-7848-4c16-8775-42cf12b44b30","Vue3生态","vue3生态",{"id":221,"name":222,"slug":223,"createdAt":224,"updatedAt":40},"c807b2c6-cb12-4409-a1f1-6bea9f330a6b","Vuex","vuex","2022-07-16T13:14:59.000Z",{"id":226,"name":227,"slug":228,"createdAt":229,"updatedAt":40},"5782dff5-2ea2-4427-9696-d4363a7fd5bc","Webpack","webpack","2022-07-16T14:33:41.000Z",{"id":231,"name":232,"slug":232,"createdAt":127,"updatedAt":40},"d0aa41f4-68f8-48d4-a4ed-3a503ea90451","下载",{"id":234,"name":235,"slug":235,"createdAt":127,"updatedAt":40},"a046060c-39ef-474a-8c85-2546aca0e2e5","代码片段",{"id":237,"name":238,"slug":238,"createdAt":127,"updatedAt":40},"fee73435-b2be-4b55-85b1-d133ea96aea4","伪元素",{"id":240,"name":241,"slug":241,"createdAt":127,"updatedAt":40},"436bd369-8c57-4869-8827-e88e50e5e0ab","伪类",{"id":243,"name":244,"slug":244,"createdAt":127,"updatedAt":40},"4c6be544-8a00-4445-92a3-e3dcbaf6142e","动画",{"id":246,"name":247,"slug":247,"createdAt":248,"updatedAt":40},"9321a12e-ea72-49a9-a32d-5566149f812f","图片压缩","2022-08-02T00:37:47.000Z",{"id":250,"name":251,"slug":251,"createdAt":165,"updatedAt":40},"512b16fb-576a-4397-a7c5-dd20e6a8f9ca","布局",{"id":253,"name":84,"slug":84,"createdAt":127,"updatedAt":40},"f32faa96-f2ec-45c6-9a17-2c76062edcb0",{"id":255,"name":17,"slug":17,"createdAt":127,"updatedAt":40},"3c46ed3f-6d6b-4f91-bcb3-af5112860bf5",{"id":257,"name":258,"slug":258,"createdAt":127,"updatedAt":40},"dbfc086a-73a6-4560-814d-593acb61cf98","性能优化",{"id":260,"name":261,"slug":261,"createdAt":262,"updatedAt":40},"1831cd06-0d6b-48f7-94fa-324782fe23cb","拖拽","2022-07-28T12:39:13.000Z",{"id":264,"name":265,"slug":265,"createdAt":165,"updatedAt":40},"9a74300d-06f7-46d0-80d9-8fe67ec0539b","数组",{"id":267,"name":268,"slug":268,"createdAt":269,"updatedAt":40},"19ac8998-7e0a-459b-9702-bb1adca70e8c","文本复制","2022-07-17T01:54:45.000Z",{"id":271,"name":272,"slug":272,"createdAt":273,"updatedAt":40},"5ff33473-71a4-4e02-8c82-f9ea369a768f","时间","2022-07-17T01:51:12.000Z",{"id":275,"name":276,"slug":276,"createdAt":127,"updatedAt":40},"aa47ca4d-d3f6-4cac-b495-2c67c9592c36","最新优惠",{"id":278,"name":279,"slug":279,"createdAt":165,"updatedAt":40},"f6766d54-54fc-405e-932d-b7d550559125","服务器",{"id":281,"name":80,"slug":80,"createdAt":127,"updatedAt":40},"d856559a-03ff-40b4-980d-3f272b998c3c",{"id":23,"name":24,"slug":24,"createdAt":127,"updatedAt":40},{"id":284,"name":105,"slug":105,"createdAt":127,"updatedAt":40},"38e1fd6b-d7c6-4d62-bf70-7bacc175bea9",{"id":286,"name":287,"slug":287,"createdAt":288,"updatedAt":40},"be7b10bc-49eb-4a03-bea7-ceb915d500fe","规范","2022-07-16T14:41:06.000Z",{"id":290,"name":291,"slug":291,"createdAt":165,"updatedAt":40},"b42e2916-ad62-4b8a-a863-cd8c19a829de","面试",{"id":293,"name":294,"slug":294,"createdAt":165,"updatedAt":40},"7069add9-b636-44f1-9cd4-ea3a6d2b85d3","面试题",104,48,[298,310,322,335,348],{"id":299,"title":300,"slug":300,"content":301,"excerpt":300,"coverImage":302,"status":9,"isPinned":10,"pinnedAt":11,"viewCount":303,"publishedAt":304,"createdAt":304,"updatedAt":305,"category":306,"author":307,"tags":308},"a1bd1f49-6f6d-4fea-9789-b5636e19a6b3","uni-app瀑布流","## 实现思路\n> 获取父组件的列表数组，watch监听数组长度变化，截取后面新的数据，创建两个左右数组，比较左右dom的长度，哪个短，就push一条数据进去，源数组删除一条数据。利用img的load（加载成功）和error方法（加载失败），触发数组的push，实现瀑布流\n\n## 代码实现\n\n```language\n\u003Ctemplate>\n    \u003Cview class=\\\"waterfall\\\">\n        \u003Cview class=\\\"waterfall_left\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in leftList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n        \u003Cview class=\\\"waterfall_right\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in rightList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n    \u003C\u002Fview>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport Search from '@\u002Fcomponents\u002Fsearch\u002Fsearch.vue';\nexport default {\n    components: { Search },\n    props: {\n        list: {\n            type: Array,\n            default: () => []\n        }\n    },\n    data() {\n        return {\n            \u002F\u002F 左侧列表\n            leftList: [],\n            \u002F\u002F 右侧列表\n            rightList: [],\n            \u002F\u002F 组件数据备份\n            newList: [],\n            \u002F\u002F默认请求数,主要为了正常排序\n            interceptNumber: 10\n        };\n    },\n    created() {\n        this.touchOff(); \u002F\u002F 触发排列\n    },\n    mounted() {},\n    watch: {\n        list(newValue, oldValue) {\n            this.interceptNumber = newValue.length - oldValue.length;\n            this.touchOff();\n        }\n    },\n    computed: {},\n    methods: {\n        \u002F\u002F 触发重新排列\n        touchOff() {\n            this.newList = [...this.list.slice(-this.interceptNumber)];\n            if (this.newList.length !== 0) {\n                this.leftList.push(this.newList.shift()); \u002F\u002F触发排列\n            }\n        },\n        \u002F\u002F 计算排列\n        considerPush() {\n            this.$nextTick(() => {\n                if (this.newList.length == 0) return; \u002F\u002F没有数据了\n                let leftH = 0;\n                let rightH = 0; \u002F\u002F左右高度\n                let query = uni.createSelectorQuery().in(this);\n                query.selectAll('.waterfall_left').boundingClientRect();\n                query.selectAll('.waterfall_right').boundingClientRect();\n                query.exec(res => {\n                    leftH = res[0].length != 0 ? res[0][0].height : 0; \u002F\u002F防止查询不到做个处理\n                    rightH = res[1].length != 0 ? res[1][0].height : 0;\n                    if (leftH == rightH || leftH \u003C rightH) {\n                        \u002F\u002F 相等 || 左边小\n                        this.leftList.push(this.newList.shift());\n                    } else {\n                        \u002F\u002F 右边小\n                        this.rightList.push(this.newList.shift());\n                    }\n\n                    \u002F\u002F console.log('左右高度：', leftH, rightH, leftH == rightH || leftH \u003C rightH);\n                });\n            });\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle scoped lang=\\\"scss\\\">\n.waterfall {\n    display: flex;\n    align-items: flex-start;\n    justify-content: flex-start;\n    .waterfall_left {\n        flex: 1;\n    }\n\n    .waterfall_right {\n        flex: 1;\n    }\n}\n\u003C\u002Fstyle>\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F7d4fa8d1d2775177b882a4656e3a5ed5.png",1819,"2022-11-22T07:54:41.000Z","2026-06-27T11:37:20.140Z",{"id":61,"name":62,"slug":63},{"id":19,"name":20,"avatar":11},[309],{"id":139,"name":140,"slug":141},{"id":311,"title":312,"slug":312,"content":313,"excerpt":312,"coverImage":314,"status":9,"isPinned":10,"pinnedAt":11,"viewCount":315,"publishedAt":316,"createdAt":316,"updatedAt":317,"category":318,"author":319,"tags":320},"b2c46bf6-d971-4cce-b21b-052dbea8e8a2","v-html使用img点击实现放大效果","## 代码实现\n```js\n\u002F**\n * JS获取html代码中所有的图片地址\n * @param htmlstr\n * @returns arr 数组\n *\u002F\n\nexport function getimgsrc(htmlstr) {\n    let reg = \u002F\u003Cimg.+?src=('|\\\")?([^'\\\"]+)('|\\\")?(?:\\s+|>)\u002Fg;\n    let arr = [];\n    let tem = 0;\n    \u002F\u002Feslint-disable-next-line\n    while ((tem = reg.exec(htmlstr))) {\n        arr.push(tem[2]); \u002F\u002F eslint-disable-line\n    }\n\n    return arr;\n}\n\n```\n\n\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\\\"image-expansion\\\" :class=\\\"classArr\\\">\n        \u003Cdiv @click.stop=\\\"hanldeImage($event)\\\" v-html=\\\"formatHtmlData\\\">\u003C\u002Fdiv>\n\n        \u003Cel-image-viewer\n            v-if=\\\"imgPreviewUrl\\\"\n            :initial-index=\\\"subscript\\\"\n            :src=\\\"imgPreviewUrl\\\"\n            :on-close=\\\"closeViewer\\\"\n            :url-list=\\\"imgList\\\"\n        >\u003C\u002Fel-image-viewer>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { getimgsrc } from '..\u002F..\u002Futils\u002Fgetimgsrc';\nimport ElImageViewer from 'element-ui\u002Fpackages\u002Fimage\u002Fsrc\u002Fimage-viewer';\nexport default {\n    components: {\n        ElImageViewer\n    },\n    props: {\n        htmlData: {\n            type: String,\n            default: () => {\n                return '';\n            }\n        },\n        classArr: {\n            type: Array,\n            default: () => {\n                return ['min'];\n            }\n        },\n\n        isArticle: {\n            type: Boolean,\n            default: () => {\n                return false;\n            }\n        }\n    },\n    data() {\n        return {\n            imgList: [],\n            formatHtmlData: '',\n            imgPreviewUrl: '',\n            subscript: 0\n        };\n    },\n    computed: {},\n\n    watch: {\n        \u002F\u002F监听数据，防止数据不更新\n        htmlData: {\n            handler(newName, oldName) {\n                \u002F\u002F判断是否为文章\n                if (this.isArticle) {\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C([^>]+)>)\u002Fgi, '').replace(\u002F[\\\r\\\n]\u002Fg, '')) : '';\n                } else {\n                    \u002F\u002F剔除strong和p标签\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C\\\u002F?strong.*?>)|(\u003C\\\u002F?p.*?>)\u002Fg, '')) : '';\n\n                    \u002F\u002F获取html全部图片，push成图片数组\n                    this.imgList = Object.values(getimgsrc(this.formatHtmlData));\n                    \u002F\u002F获取图片下标\n                    let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                    this.subscript = subscript > -1 ? subscript : 0;\n                }\n            },\n\n            immediate: true\n        }\n    },\n\n    mounted() {},\n\n    methods: {\n        \u002F\u002F监听点击事件\n        hanldeImage(event) {\n            if (event.target.nodeName === 'IMG' || event.target.nodeName === 'img') {\n                \u002F\u002F获取点击的图片url,decodeURIComponent转码一下，防禁url转码\n                this.imgPreviewUrl = decodeURIComponent(event.target.currentSrc);\n\n                \u002F\u002F获取图片下标\n                let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                this.subscript = subscript > -1 ? subscript : 0;\n\n                \u002F\u002F禁止遮罩层后面的内容滚动\n                document.documentElement.style.overflowY = 'hidden';\n            } else {\n                this.$emit('goDetail');\n            }\n        },\n\n        \u002F\u002F关闭弹框\n        closeViewer() {\n            this.imgPreviewUrl = '';\n            \u002F\u002F恢复遮罩层后面的内容滚动\n            document.documentElement.style.overflowY = 'auto';\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle lang=\\\"scss\\\" scoped>\n.image-expansion {\n}\n\n.min {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n        height: 28px;\n        padding: 0 10px 3px;\n    }\n}\n\n.max {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n    }\n}\n\n.class1 {\n    \u002Fdeep\u002F div {\n        font-size: 15px;\n        font-family: Microsoft YaHei;\n        font-weight: 400;\n        color: #888888;\n        line-height: 30px;\n    }\n}\n\u003C\u002Fstyle>\n\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fc77c3fb113d1ab2f67e7afba1ca33b95.png",1572,"2023-01-10T07:22:29.000Z","2026-06-27T11:07:40.499Z",{"id":87,"name":88,"slug":89},{"id":19,"name":20,"avatar":11},[321],{"id":246,"name":247,"slug":247},{"id":323,"title":324,"slug":325,"content":326,"excerpt":324,"coverImage":327,"status":9,"isPinned":10,"pinnedAt":11,"viewCount":328,"publishedAt":329,"createdAt":329,"updatedAt":330,"category":331,"author":332,"tags":333},"9f33b49b-0785-4404-9a4e-d85937e7fc92","在 vue3 中优雅的使用 jsx\u002Ftsx","在-vue3-中优雅的使用-jsx-tsx","## 安装插件（@vitejs\u002Fplugin-vue-jsx）\n```shell\npnpm add @vitejs\u002Fplugin-vue-jsx -D\n```\n\n## 配置vite.config.ts\n```ts\nimport vueJsx from \\\"@vitejs\u002Fplugin-vue-jsx\\\";\n\nexport default defineConfig({\n  plugins: [\n    vueJsx(),\n  ]\n})\n```\n\n## 插值\n```language\n\u002F\u002F vue3模板语法\n\u003Cspan>{{ a + b }}\u003C\u002Fspan>\n\n\u002F\u002F jsx\u002Ftsx\n\u003Cspan>{ a + b }\u003C\u002Fspan>\n```\n\n\n## class与style 绑定\n\n```ts\n\u002F\u002F 模板字符串\n\u003Cdiv className={`header ${ isBg ? 'headerBg' : '' }`}>header\u003C\u002Fdiv>\n\u002F\u002F数组\n\u003Cdiv class={ [ 'header', isBg && 'headerBg' ] } >header\u003C\u002Fdiv>\n```\n\n```ts\nconst color = 'red'\nconst element = \u003Csapn style={{ color, fontSize: '16px' }}>style\u003C\u002Fsapn>\n```\n\n## 条件渲染\n\n```ts\n   setup() {\n       const isShow = false\n       const element = () => {\n           if (isShow) {\n               return \u003Cspan>我是if\u003C\u002Fspan>\n           } else {\n               return \u003Cspan>我是else\u003C\u002Fspan>\n           }\n       }\n       return () => (\n           \u003Cdiv>\n               \u003Cspan v-show={isShow}>我是v-show\u003C\u002Fspan>\n               {\n                   element()\n               }\n               {\n                   isShow ? \u003Cp>我是三目1\u003C\u002Fp> : \u003Cp>我是三目2\u003C\u002Fp>\n               }\n           \u003Cdiv>\n       )\n   }\n```\n\n## 列表渲染\n```language\nsetup() {\n   const listData = [\n       {name: 'Tom', age: 18},\n       {name: 'Jim', age: 20},\n       {name: 'Lucy', age: 16}\n   ]\n   return () => (\n       \u003Cdiv>\n           \u003Cdiv class={'box'}>\n               \u003Cspan>姓名\u003C\u002Fspan>\n               \u003Cspan>年龄\u003C\u002Fspan>\n           \u003C\u002Fdiv>\n           {\n               prop.listData.map(item => {\n                   return \u003Cdiv class={'box'}>\n                       \u003Cspan>{item.name}\u003C\u002Fspan>\n                       \u003Cspan>{item.age}\u003C\u002Fspan>\n                   \u003C\u002Fdiv>\n               })\n           }\n       \u003C\u002Fdiv>\n   )\n}\n\n```\n\n## 事件处理\n\n```language\nsetup() {\n    const clickBox = val => {\n        console.log(val)\n    }\n    return () => (\n        \u003Cdiv class={'box1'} onClick={() => clickBox('box1')}>\n            \u003Cspan>我是box1\u003C\u002Fspan>\n            \u003Cdiv class={'box2'} onClick={() => clickBox('box2')}>\n                \u003Cspan>我是box2\u003C\u002Fspan>\n                \u003Cdiv class={'box3'} onClick={withModifiers(() => clickBox('box3'), ['stop'])}>我是box3\u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    )\n}\n```\n\n## v-model\n\n```ts\n\u002F\u002F 正常写法\n\u003Cinput v-model=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={value} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 指定绑定值写法\n\u003Cinput v-model:modelValue=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue']} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 修饰符写法\n\u003Cinput v-model:modelValue.trim=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue',['trim']]} \u002F> \u002F\u002F jsx\n```\n\n## slot插槽\n\n### 定义插槽\n```ts\nimport { renderSlot } from \\\"vue\\\"\nexport default defineComponent({\n    \u002F\u002F 从ctx中解构出来 slots\n    setup(props, { slots }) {\n        return () => (\n            \u003Cdiv>\n                { renderSlot(slots, 'default') }\n                { slots.title?.() }\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n### 使用插槽\n\n```ts\nimport Vslot from '.\u002FslotTem'\nexport default defineComponent({\n    setup() {\n        return () => (\n            \u003Cdiv class={'box'}>\n                \u003CVslot v-slots={{\n                    title: () => {\n                        return \u003Cp>我是title插槽\u003C\u002Fp>\n                    },\n                    default: () => {\n                        return \u003Cp>我是default插槽\u003C\u002Fp>\n                    }\n                }} \u002F>\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F3e4cb0d3df611ee3f57b8ed503e1015e.png",1392,"2023-04-19T09:23:27.000Z","2026-06-27T11:08:34.303Z",{"id":54,"name":55,"slug":56},{"id":19,"name":20,"avatar":11},[334],{"id":215,"name":55,"slug":56},{"id":336,"title":337,"slug":338,"content":339,"excerpt":337,"coverImage":340,"status":9,"isPinned":10,"pinnedAt":11,"viewCount":341,"publishedAt":342,"createdAt":342,"updatedAt":343,"category":344,"author":345,"tags":346},"1dcdd755-0e46-45a3-8998-9a213fd3fcd5","Vue3 导入导出Excel","vue3-导入导出excel","## 安装\n```shell\npnpm add -S XLSX\n```\n\n\n## 方法封装\n```js\nimport * as XLSX from 'xlsx';\n\n\u002F\u002F参数说明\n\u002F\u002Fconfiguration: {\n\u002F\u002F  data: [], \u002F\u002F 导出的数据\n\u002F\u002F  head: {}, \u002F\u002F 导出的数据对应的表头\n\u002F\u002F  name: '', \u002F\u002F 导出的文件名\n\u002F\u002F  label: '', \u002F\u002F 导出的表单名\n\u002F\u002F  widthArr: [], \u002F\u002F 导出的表单列宽\n\u002F\u002F}\n\n\u002F\u002F 导出excel\nexport const ExportXlsx = (configuration) => {\n  const { data, head, name, label, widthArr } = configuration;\n\n  const list = data.map((item) => {\n    const obj = {};\n    for (const k in item) {\n      if (head[k]) {\n        obj[head[k]] = item[k];\n      }\n    }\n    return obj;\n  });\n\n  \u002F\u002F 创建工作表\n  const xLSXData = XLSX.utils.json_to_sheet(list);\n  \u002F\u002F 创建工作簿\n  const wb = XLSX.utils.book_new();\n  \u002F\u002F 将工作表放入工作簿中\n  XLSX.utils.book_append_sheet(wb, xLSXData, label);\n  xLSXData['!cols'] = [];\n  \u002F\u002F 设置列宽\n  widthArr.forEach((item) => {\n    xLSXData['!cols'].push({ wpx: item });\n  });\n\n  \u002F\u002F 生成文件并下载\n  XLSX.writeFile(wb, `${name}.xlsx`);\n};\n\n\u002F\u002F 导入excel\nexport const ImportXlsx = (e) => {\n  const file = e.target.files[0];\n  const reader = new FileReader();\n  reader.readAsArrayBuffer(file);\n  reader.onload = (e) => {\n    const data = e.target.result;\n    const workbook = XLSX.read(data, { type: 'binary', cellDates: true });\n    const wsname = workbook.SheetNames[0];\n    const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[wsname]);\n    console.log(outdata);\n  };\n};\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F80231a326d3846b895a7d46c99e738ce.png",1336,"2023-03-14T06:18:59.000Z","2026-06-27T11:37:23.273Z",{"id":61,"name":62,"slug":63},{"id":19,"name":20,"avatar":11},[347],{"id":139,"name":140,"slug":141},{"id":349,"title":350,"slug":351,"content":352,"excerpt":350,"coverImage":353,"status":9,"isPinned":10,"pinnedAt":11,"viewCount":354,"publishedAt":355,"createdAt":355,"updatedAt":356,"category":357,"author":361,"tags":362},"80920598-a452-4357-bf53-842b200560e8","React18入门到精通教程","react18入门到精通教程","\n## JSX实现列表渲染\n> 使用`map()`方法遍历数组，必须添加`key`属性提高性能\n```jsx\nconst songs = [\n  { id: 1, name: \\\"helo1\\\" },\n  { id: 2, name: \\\"helo2\\\" },\n  { id: 3, name: \\\"helo3\\\" },\n];\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cul>\n        {songs.map((song) => (\n          \u002F\u002F 关键：添加唯一key标识符（避免使用索引）\n          \u003Cli key={song.id}>  \n            {song.id}-{song.name}\n          \u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**最佳实践：**\n1. 使用`\u003Cul>`包裹列表项\n2. `key`应使用稳定唯一标识（如ID），避免数组索引\n3. 空列表处理：`{songs.length > 0 && ...}` 或 `{songs.map(...) || \u003CEmptyView\u002F>}`\n\n## JSX实现条件渲染\n### 简单逻辑\n```jsx\n\u002F\u002F 三元表达式\n{isLoggedIn ? \u003CDashboard \u002F> : \u003CLoginForm \u002F>}\n\n\u002F\u002F 逻辑短路\n{hasNotification && \u003CNotificationBadge count={5} \u002F>}\n\n\u002F\u002F 空值处理\n{userProfile?.avatar || \u003CDefaultAvatar \u002F>}\n```\n\n### 复杂逻辑\n```jsx\nconst renderContent = (type) => {\n  switch(type) {\n    case 'success': \n      return \u003CSuccessAlert \u002F>;\n    case 'error':\n      return \u003CErrorAlert \u002F>;\n    default:\n      return \u003CInfoAlert \u002F>;\n  }\n}\n\nfunction App() {\n  return (\n    \u003Cdiv className=\\\"container\\\">\n      {renderContent(status)}\n      \n      {\u002F* 另一种模式：立即执行函数 *\u002F}\n      {(() => {\n        if (isLoading) return \u003CSpinner \u002F>;\n        if (isEmpty) return \u003CEmptyState \u002F>;\n        return \u003CDataTable \u002F>;\n      })()}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n## JSX样式处理\n### 行内样式\n```jsx\n\u002F\u002F 直接对象\n\u003Cdiv style={{ \n  color: 'white', \n  backgroundColor: 'teal',\n  padding: '1rem'\n}}>\n\n\u002F\u002F 样式对象复用\nconst alertStyle = {\n  padding: '15px',\n  borderRadius: '4px',\n  margin: '10px 0'\n};\n\nfunction Alert({ type }) {\n  return (\n    \u003Cdiv style={{\n      ...alertStyle,  \u002F\u002F 扩展运算符合并样式\n      background: type === 'error' ? '#f8d7da' : '#d4edda'\n    }}>\n      {message}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n### 类名控制（推荐）\n```css\n\u002F* styles.module.css *\u002F\n.card {\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  padding: 20px;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.highlight {\n  background-color: #ffffe0;\n}\n```\n\n```jsx\nimport styles from '.\u002Fstyles.module.css';\n\nfunction ProductCard({ featured }) {\n  return (\n    \u003Cdiv className={`${styles.card} ${featured ? styles.highlight : ''}`}>\n      {\u002F* 内容 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## React 18新特性\n### 并发模式（Concurrent Mode）\n```jsx\nimport { startTransition } from 'react';\n\n\u002F\u002F 非紧急状态更新\nfunction handleSearch(query) {\n  startTransition(() => {\n    setSearchQuery(query); \u002F\u002F 可中断的渲染\n  });\n}\n```\n\n### 自动批处理（Automatic Batching）\n```jsx\n\u002F\u002F React 17及之前：两次渲染\n\u002F\u002F React 18：自动批处理，一次渲染\nfunction handleClick() {\n  setCount(c => c + 1);\n  setFlag(f => !f);\n}\n```\n\n## Redux状态管理（现代写法）\n### 安装依赖\n```bash\nnpm install @reduxjs\u002Ftoolkit react-redux\n```\n\n### 创建Store\n```js\n\u002F\u002F store.js\nimport { configureStore, createSlice } from '@reduxjs\u002Ftoolkit';\n\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment: state => { state.value += 1 },\n    decrement: state => { state.value -= 1 },\n    incrementByAmount: (state, action) => {\n      state.value += action.payload\n    }\n  }\n});\n\n\u002F\u002F 异步操作示例\nexport const fetchUserData = () => async (dispatch) => {\n  const response = await fetch('\u002Fapi\u002Fuser');\n  dispatch(setUser(await response.json()));\n};\n\nexport const store = configureStore({\n  reducer: {\n    counter: counterSlice.reducer,\n    \u002F\u002F 其他reducer...\n  }\n});\n\nexport const { increment, decrement } = counterSlice.actions;\n```\n\n### 组件集成\n```jsx\n\u002F\u002F index.js\nimport { Provider } from 'react-redux';\nimport { store } from '.\u002Fstore';\n\nroot.render(\n  \u003CProvider store={store}>\n    \u003CApp \u002F>\n  \u003C\u002FProvider>\n);\n\n\u002F\u002F Counter.js\nimport { useSelector, useDispatch } from 'react-redux';\nimport { increment, decrement } from '.\u002Fstore';\n\nfunction Counter() {\n  const count = useSelector(state => state.counter.value);\n  const dispatch = useDispatch();\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => dispatch(decrement())}>-\u003C\u002Fbutton>\n      \u003Cspan>{count}\u003C\u002Fspan>\n      \u003Cbutton onClick={() => dispatch(increment())}>+\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 最佳实践总结\n1. **组件设计**：遵循单一职责原则，拆分智能组件（容器组件）和展示组件\n2. **状态管理**：\n   - 局部状态用`useState`\u002F`useReducer`\n   - 全局共享状态用Redux\n   - 避免过度使用状态提升\n3. **性能优化**：\n   - 使用`React.memo`记忆组件\n   - 使用`useCallback`\u002F`useMemo`避免不必要的重渲染\n   - 虚拟化长列表（react-window）\n4. **Hooks规范**：\n   - 避免在循环\u002F条件中使用Hook\n   - 自定义Hook以`use`前缀命名\n5. **TypeScript集成**：\n   ```tsx\n   interface UserCardProps {\n     name: string;\n     age: number;\n     onSelect: (id: string) => void;\n   }\n   \n   const UserCard: React.FC\u003CUserCardProps> = ({ name, age }) => (\n     \u003Cdiv>{name} ({age})\u003C\u002Fdiv>\n   )\n   ```\n\n下面为您完善教程，增加React路由和生命周期相关内容：\n\n## React路由管理（React Router v6）\n\n### 安装与基础配置\n```bash\nnpm install react-router-dom@6\n```\n\n### 路由基础结构\n```jsx\n\u002F\u002F index.js\nimport { BrowserRouter } from 'react-router-dom';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  \u003CBrowserRouter>\n    \u003CApp \u002F>\n  \u003C\u002FBrowserRouter>\n);\n\n\u002F\u002F App.js\nimport { Routes, Route, Link } from 'react-router-dom';\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003CLink to=\\\"\u002F\\\">首页\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fabout\\\">关于\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fusers\\\">用户列表\u003C\u002FLink>\n      \u003C\u002Fnav>\n      \n      \u003CRoutes>\n        \u003CRoute path=\\\"\u002F\\\" element={\u003CHome \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fabout\\\" element={\u003CAbout \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\\\" element={\u003CUserList \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\u002F:id\\\" element={\u003CUserDetail \u002F>} \u002F>\n        \u003CRoute path=\\\"*\\\" element={\u003CNotFound \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 嵌套路由\n```jsx\n\u002F\u002F Dashboard.js\nimport { Outlet } from 'react-router-dom';\n\nfunction Dashboard() {\n  return (\n    \u003Cdiv>\n      \u003Ch2>仪表盘\u003C\u002Fh2>\n      \u003COutlet \u002F> {\u002F* 子路由渲染位置 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F 路由配置\n\u003CRoutes>\n  \u003CRoute path=\\\"\u002Fdashboard\\\" element={\u003CDashboard \u002F>}>\n    \u003CRoute index element={\u003CDashboardHome \u002F>} \u002F>\n    \u003CRoute path=\\\"settings\\\" element={\u003CDashboardSettings \u002F>} \u002F>\n    \u003CRoute path=\\\"analytics\\\" element={\u003CDashboardAnalytics \u002F>} \u002F>\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n```\n\n### 编程式导航\n```jsx\nimport { useNavigate, useParams, useLocation } from 'react-router-dom';\n\nfunction UserCard({ user }) {\n  const navigate = useNavigate();\n  \n  return (\n    \u003Cdiv onClick={() => navigate(`\u002Fusers\u002F${user.id}`)}>\n      {user.name}\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction UserDetail() {\n  const { id } = useParams(); \u002F\u002F 获取URL参数\n  const location = useLocation(); \u002F\u002F 获取位置对象\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>用户ID: {id}\u003C\u002Fh2>\n      \u003Cp>当前路径: {location.pathname}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 路由守卫（认证保护）\n```jsx\nimport { Navigate } from 'react-router-dom';\n\nfunction ProtectedRoute({ children }) {\n  const { isAuthenticated } = useAuth();\n  \n  if (!isAuthenticated) {\n    return \u003CNavigate to=\\\"\u002Flogin\\\" replace \u002F>;\n  }\n  \n  return children;\n}\n\n\u002F\u002F 使用\n\u003CRoute \n  path=\\\"\u002Fdashboard\\\" \n  element={\n    \u003CProtectedRoute>\n      \u003CDashboard \u002F>\n    \u003C\u002FProtectedRoute>\n  } \n\u002F>\n```\n\n## React生命周期\n\n### 类组件生命周期方法\n\n```jsx\nclass LifecycleDemo extends React.Component {\n  \u002F\u002F 1. 初始化阶段\n  constructor(props) {\n    super(props);\n    this.state = { count: 0 };\n    console.log('Constructor');\n  }\n\n  \u002F\u002F 2. 挂载阶段\n  componentDidMount() {\n    console.log('Component did mount');\n    \u002F\u002F 适合进行API调用、事件订阅\n    this.timer = setInterval(() => {\n      this.setState(prev => ({ count: prev.count + 1 }));\n    }, 1000);\n  }\n\n  \u002F\u002F 3. 更新阶段\n  shouldComponentUpdate(nextProps, nextState) {\n    console.log('Should component update?');\n    return nextState.count !== this.state.count;\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    console.log('Component did update');\n  }\n\n  \u002F\u002F 4. 卸载阶段\n  componentWillUnmount() {\n    console.log('Component will unmount');\n    \u002F\u002F 清理操作\n    clearInterval(this.timer);\n  }\n\n  render() {\n    console.log('Render');\n    return \u003Cdiv>Count: {this.state.count}\u003C\u002Fdiv>;\n  }\n}\n```\n\n### 函数组件生命周期（Hooks实现）\n\n```jsx\nimport { useState, useEffect } from 'react';\n\nfunction FunctionLifecycle() {\n  const [count, setCount] = useState(0);\n  const [data, setData] = useState(null);\n\n  \u002F\u002F 相当于componentDidMount + componentDidUpdate\n  useEffect(() => {\n    console.log('每次渲染后执行');\n  });\n\n  \u002F\u002F 相当于componentDidMount\n  useEffect(() => {\n    console.log('组件挂载后执行');\n    \n    \u002F\u002F 数据获取\n    fetch('\u002Fapi\u002Fdata')\n      .then(res => res.json())\n      .then(setData);\n    \n    \u002F\u002F 相当于componentWillUnmount\n    return () => {\n      console.log('组件卸载前执行');\n    };\n  }, []); \u002F\u002F 空依赖数组\n\n  \u002F\u002F 依赖变化时执行\n  useEffect(() => {\n    console.log('count变化时执行:', count);\n    \n    document.title = `Count: ${count}`;\n    \n    return () => {\n      console.log('清理count效果');\n    };\n  }, [count]); \u002F\u002F count依赖\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Count: {count}\u003C\u002Fp>\n      \u003Cbutton onClick={() => setCount(c => c + 1)}>增加\u003C\u002Fbutton>\n      {data && \u003Cpre>{JSON.stringify(data, null, 2)}\u003C\u002Fpre>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 生命周期阶段对比\n\n| 阶段 | 类组件方法 | 函数组件Hook |\n|------|------------|--------------|\n| **挂载** | constructor | useState初始化 |\n|       | render      | 函数体执行    |\n|       | componentDidMount | useEffect(() => {}, []) |\n| **更新** | shouldComponentUpdate | React.memo, useMemo |\n|       | render      | 函数体执行    |\n|       | componentDidUpdate | useEffect(() => {}) |\n| **卸载** | componentWillUnmount | useEffect返回函数 |\n| **错误处理** | componentDidCatch | 暂无直接等效，需错误边界组件 |\n\n### 现代React开发建议\n\n1. **优先使用函数组件+Hooks**：\n   - 90%的场景可替代类组件\n   - 更简洁的代码结构\n   - 更好的逻辑复用\n\n2. **关键生命周期替代**：\n   - `componentDidMount` → `useEffect(() => {}, [])`\n   - `componentDidUpdate` → `useEffect(() => {})` 或带依赖的 `useEffect`\n   - `componentWillUnmount` → `useEffect(() => { return cleanup }, [])`\n   - `shouldComponentUpdate` → `React.memo` 或 `useMemo`\n\n3. **数据获取最佳实践**：\n```jsx\nuseEffect(() => {\n  let isMounted = true;\n  \n  const fetchData = async () => {\n    try {\n      const result = await api.getData();\n      if (isMounted) setData(result);\n    } catch (error) {\n      if (isMounted) setError(error);\n    }\n  };\n  \n  fetchData();\n  \n  return () => {\n    isMounted = false; \u002F\u002F 避免组件卸载后设置状态\n  };\n}, []);\n```\n\n## 路由与生命周期整合示例\n\n```jsx\nfunction UserProfile() {\n  const { id } = useParams();\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    let isActive = true;\n    \n    const fetchUser = async () => {\n      try {\n        setLoading(true);\n        const data = await fetchUserById(id);\n        if (isActive) {\n          setUser(data);\n          setLoading(false);\n        }\n      } catch (error) {\n        if (isActive) {\n          setError(error.message);\n          setLoading(false);\n        }\n      }\n    };\n    \n    fetchUser();\n    \n    return () => {\n      isActive = false; \u002F\u002F 清理效果\n    };\n  }, [id]); \u002F\u002F id变化时重新获取\n\n  if (loading) return \u003CSpinner \u002F>;\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>{user.name}\u003C\u002Fh2>\n      \u003Cp>Email: {user.email}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 完整项目结构建议\n\n```\nsrc\u002F\n├── components\u002F      # 通用UI组件\n├── pages\u002F           # 页面组件\n├── layouts\u002F         # 布局组件\n├── hooks\u002F           # 自定义Hooks\n├── store\u002F           # Redux状态\n│   ├── slices\u002F\n│   └── store.js\n├── services\u002F        # API服务\n├── routers\u002F         # 路由配置\n├── utils\u002F           # 工具函数\n├── assets\u002F          # 静态资源\n└── App.js           # 主应用组件\n```\n\n这些新增内容涵盖了React路由的现代用法（v6版本）以及React生命周期的详细解释，包括类组件和函数组件的实现方式对比。同时还提供了路由与生命周期整合的实际示例，帮助开发者理解如何在真实项目中应用这些概念。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F46e205aa1bd33d1bb7201019fc2fdf43.png",1148,"2023-02-21T02:01:52.000Z","2026-06-27T11:00:41.557Z",{"id":358,"name":359,"slug":360},"f1701085-b8c1-413a-8750-58e7a0a33832","React","react",{"id":19,"name":20,"avatar":11},[363],{"id":183,"name":184,"slug":185},{"list":365,"total":32,"page":34,"pageSize":366},[],20]