📚 Java 面试题(30道)
一、Java基础(10道)
1. Java中的八大基本数据类型及其包装类是什么?
答案:
- byte (Byte) - 8位
- short (Short) - 16位
- int (Integer) - 32位
- long (Long) - 64位
- float (Float) - 32位
- double (Double) - 64位
- char (Character) - 16位
- boolean (Boolean) - 1位
示例代码:
public class PrimitiveTypes {
public static void main(String[] args) {
// 基本类型
int num = 10;
double price = 99.99;
// 自动装箱
Integer numObj = num;
// 自动拆箱
int value = numObj;
System.out.println("Value: " + value);
}
}
2. == 和 equals() 的区别是什么?
答案:
==比较的是引用地址(对于对象)或值(对于基本类型)equals()比较的是对象的内容
示例代码:
public class EqualsDemo {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = "hello";
String s4 = "hello";
System.out.println(s1 == s2); // false(不同对象)
System.out.println(s1.equals(s2)); // true(内容相同)
System.out.println(s3 == s4); // true(字符串常量池)
}
}
3. String、StringBuilder、StringBuffer的区别?
答案:
- String: 不可变,线程安全,适合少量字符串操作
- StringBuilder: 可变,非线程安全,性能最好,适合单线程
- StringBuffer: 可变,线程安全(synchronized),适合多线程
示例代码:
public class StringComparison {
public static void main(String[] args) {
// String - 每次操作都创建新对象
String str = "Hello";
str += " World"; // 创建新对象
// StringBuilder - 单线程最佳选择
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
// StringBuffer - 多线程安全
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World");
System.out.println(str);
System.out.println(sb.toString());
System.out.println(sbf.toString());
}
}
4. Java中的四种引用类型?
答案:
- 强引用(Strong Reference): 普通引用,不会被GC回收
- 软引用(Soft Reference): 内存不足时会被回收
- 弱引用(Weak Reference): GC时会被回收
- 虚引用(Phantom Reference): 无法通过引用获取对象
示例代码:
import java.lang.ref.*;
public class ReferenceDemo {
public static void main(String[] args) {
// 强引用
Object strongRef = new Object();
// 软引用
SoftReference<Object> softRef = new SoftReference<>(new Object());
// 弱引用
WeakReference<Object> weakRef = new WeakReference<>(new Object());
// 虚引用
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
System.out.println("Soft: " + softRef.get());
System.out.println("Weak: " + weakRef.get());
System.out.println("Phantom: " + phantomRef.get()); // 总是null
}
}
5. final、finally、finalize的区别?
答案:
- final: 关键字,修饰类(不可继承)、方法(不可重写)、变量(常量)
- finally: 异常处理中的代码块,总是执行
- finalize(): Object类的方法,GC前调用
示例代码:
public class FinalDemo {
// final变量
private final int MAX_SIZE = 100;
// final方法
public final void display() {
System.out.println("Cannot be overridden");
}
// 异常处理
public void testFinally() {
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Exception caught");
} finally {
System.out.println("Finally always executes");
}
}
// finalize方法
@Override
protected void finalize() throws Throwable {
System.out.println("Object is being garbage collected");
super.finalize();
}
}
// final类
final class ImmutableClass {
// 不能被继承
}
6. Java中的异常体系?
答案:
- Throwable : 所有异常的父类
- Error: 系统错误,无法处理(如OutOfMemoryError)
- Exception : 可以捕获处理的异常
- Checked Exception: 编译时检查(如IOException)
- Unchecked Exception: 运行时异常(如NullPointerException)
示例代码:
public class ExceptionDemo {
// 自定义异常
static class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
// 抛出checked异常
public void readFile(String path) throws IOException {
if (path == null) {
throw new IOException("Path cannot be null");
}
// 读取文件逻辑
}
// 处理异常
public void handleException() {
try {
readFile(null);
} catch (IOException e) {
System.err.println("IO Error: " + e.getMessage());
} catch (Exception e) {
System.err.println("General Error: " + e.getMessage());
} finally {
System.out.println("Cleanup code");
}
}
// try-with-resources (Java 7+)
public void autoClose() {
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
}
7. Java中的接口和抽象类的区别?
答案:
| 特性 | 接口 | 抽象类 |
|---|---|---|
| 方法 | 默认public abstract(Java 8+可有default/static) | 可以有具体实现 |
| 变量 | public static final | 可以有实例变量 |
| 构造器 | 没有 | 可以有 |
| 继承 | 支持多继承 | 单继承 |
示例代码:
// 接口
interface Animal {
void makeSound(); // 抽象方法
// Java 8+ default方法
default void sleep() {
System.out.println("Animal is sleeping");
}
// Java 8+ static方法
static void info() {
System.out.println("This is Animal interface");
}
}
// 抽象类
abstract class Vehicle {
protected String brand;
// 构造器
public Vehicle(String brand) {
this.brand = brand;
}
// 抽象方法
abstract void start();
// 具体方法
public void stop() {
System.out.println(brand + " stopped");
}
}
// 实现
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
class Car extends Vehicle {
public Car(String brand) {
super(brand);
}
@Override
void start() {
System.out.println(brand + " started");
}
}
8. Java中的重载(Overload)和重写(Override)的区别?
答案:
- 重载: 同一个类中,方法名相同,参数不同(编译时多态)
- 重写: 子类重新定义父类方法,方法签名必须相同(运行时多态)
示例代码:
class Calculator {
// 方法重载 - 参数个数不同
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
// 方法重载 - 参数类型不同
public double add(double a, double b) {
return a + b;
}
}
class Parent {
public void display() {
System.out.println("Parent display");
}
public void show(String msg) {
System.out.println("Parent: " + msg);
}
}
class Child extends Parent {
// 方法重写
@Override
public void display() {
System.out.println("Child display");
}
@Override
public void show(String msg) {
System.out.println("Child: " + msg);
}
}
9. Java中的访问修饰符?
答案:
| 修饰符 | 同类 | 同包 | 子类 | 其他包 |
|---|---|---|---|---|
| private | ✓ | ✗ | ✗ | ✗ |
| default | ✓ | ✓ | ✗ | ✗ |
| protected | ✓ | ✓ | ✓ | ✗ |
| public | ✓ | ✓ | ✓ | ✓ |
示例代码:
package com.example;
public class AccessModifiers {
private int privateVar = 1;
int defaultVar = 2;
protected int protectedVar = 3;
public int publicVar = 4;
private void privateMethod() {
System.out.println("Private method");
}
void defaultMethod() {
System.out.println("Default method");
}
protected void protectedMethod() {
System.out.println("Protected method");
}
public void publicMethod() {
System.out.println("Public method");
// 同类中可以访问所有
privateMethod();
}
}
10. Java中的泛型是什么?
答案: 泛型提供了编译时类型安全检测机制,允许在编译时检测非法类型。
示例代码:
// 泛型类
class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
// 泛型方法
class GenericMethod {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
// 泛型接口
interface Pair<K, V> {
K getKey();
V getValue();
}
// 有界类型参数
class NumberBox<T extends Number> {
private T number;
public NumberBox(T number) {
this.number = number;
}
public double getDoubleValue() {
return number.doubleValue();
}
}
// 使用示例
public class GenericsDemo {
public static void main(String[] args) {
// 泛型类使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
Box<Integer> intBox = new Box<>();
intBox.set(123);
// 泛型方法使用
Integer[] intArray = {1, 2, 3};
GenericMethod.printArray(intArray);
// 有界类型
NumberBox<Integer> nb = new NumberBox<>(100);
System.out.println(nb.getDoubleValue());
}
}
二、集合框架(5道)
11. ArrayList和LinkedList的区别?
答案:
- ArrayList: 基于动态数组,随机访问快O(1),插入删除慢O(n)
- LinkedList: 基于双向链表,插入删除快O(1),随机访问慢O(n)
示例代码:
import java.util.*;
public class ListComparison {
public static void main(String[] args) {
// ArrayList - 适合查询多
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
System.out.println(arrayList.get(1)); // O(1)
// LinkedList - 适合插入删除多
List<String> linkedList = new LinkedList<>();
linkedList.add("X");
linkedList.add(0, "Y"); // 头部插入快
linkedList.remove(0);
// 性能测试
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
arrayList.add(0, "item"); // 头部插入慢
}
System.out.println("ArrayList: " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
linkedList.add(0, "item"); // 头部插入快
}
System.out.println("LinkedList: " + (System.currentTimeMillis() - start));
}
}
12. HashMap的底层实现原理?
答案:
- Java 8之前:数组 + 链表
- Java 8之后:数组 + 链表/红黑树(链表长度>8时转为红黑树)
- 默认容量16,负载因子0.75
- 扩容:容量翻倍,重新hash
示例代码:
import java.util.*;
public class HashMapDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// 基本操作
map.put("Apple", 100);
map.put("Banana", 200);
map.put("Cherry", 300);
// 获取
System.out.println(map.get("Apple"));
// 遍历
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Java 8+ Lambda
map.forEach((k, v) -> System.out.println(k + " = " + v));
// 常用方法
map.putIfAbsent("Date", 400);
map.computeIfAbsent("Elderberry", k -> k.length());
map.merge("Apple", 50, Integer::sum);
// 自定义对象作为Key
Map<Person, String> personMap = new HashMap<>();
personMap.put(new Person("John", 25), "Engineer");
}
}
// 自定义Key需要重写equals和hashCode
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
13. HashMap、HashTable、ConcurrentHashMap的区别?
答案:
- HashMap: 非线程安全,允许null key和value,性能最好
- HashTable: 线程安全(synchronized),不允许null,已过时
- ConcurrentHashMap: 线程安全(分段锁/CAS),性能好,不允许null
示例代码:
import java.util.concurrent.*;
import java.util.*;
public class MapComparison {
public static void main(String[] args) throws InterruptedException {
// HashMap - 非线程安全
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put(null, 1); // 允许null
hashMap.put("key", null);
// ConcurrentHashMap - 线程安全
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// concurrentMap.put(null, 1); // 不允许null,会抛异常
// 多线程测试
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
final int value = i;
executor.submit(() -> {
concurrentMap.put("key" + value, value);
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("Size: " + concurrentMap.size());
}
}
14. HashSet和TreeSet的区别?
答案:
- HashSet: 基于HashMap,无序,允许null,查找O(1)
- TreeSet: 基于TreeMap(红黑树),有序,不允许null,查找O(log n)
示例代码:
import java.util.*;
public class SetDemo {
public static void main(String[] args) {
// HashSet - 无序
Set<Integer> hashSet = new HashSet<>();
hashSet.add(5);
hashSet.add(2);
hashSet.add(8);
hashSet.add(1);
System.out.println("HashSet: " + hashSet); // 无序输出
// TreeSet - 自然排序
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(2);
treeSet.add(8);
treeSet.add(1);
System.out.println("TreeSet: " + treeSet); // [1, 2, 5, 8]
// TreeSet自定义排序
Set<String> customSet = new TreeSet<>(Comparator.reverseOrder());
customSet.add("Apple");
customSet.add("Banana");
customSet.add("Cherry");
System.out.println("Custom: " + customSet); // 倒序
// TreeSet with custom object
Set<Student> students = new TreeSet<>((s1, s2) -> s1.getScore() - s2.getScore());
students.add(new Student("Alice", 85));
students.add(new Student("Bob", 92));
students.add(new Student("Charlie", 78));
students.forEach(System.out::println);
}
}
class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public int getScore() {
return score;
}
@Override
public String toString() {
return name + ": " + score;
}
}
15. Java集合的fail-fast和fail-safe机制?
答案:
- fail-fast: 快速失败,迭代时检测到修改立即抛ConcurrentModificationException(如ArrayList)
- fail-safe: 安全失败,在集合副本上迭代,不会抛异常(如ConcurrentHashMap)
示例代码:
import java.util.*;
import java.util.concurrent.*;
public class FailFastDemo {
public static void main(String[] args) {
// Fail-fast示例
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
try {
for (String item : list) {
System.out.println(item);
if ("B".equals(item)) {
list.remove(item); // 抛出ConcurrentModificationException
}
}
} catch (ConcurrentModificationException e) {
System.out.println("Fail-fast detected!");
}
// 正确的删除方式
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("B".equals(item)) {
iterator.remove(); // 使用迭代器删除
}
}
// Fail-safe示例
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// 不会抛异常
for (String key : map.keySet()) {
System.out.println(key);
if ("B".equals(key)) {
map.remove(key); // 安全删除
}
}
}
}
三、多线程与并发(8道)
16. 创建线程的几种方式?
答案:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口(有返回值)
- 线程池
示例代码:
import java.util.concurrent.*;
public class ThreadCreation {
public static void main(String[] args) throws Exception {
// 方式1: 继承Thread
Thread thread1 = new MyThread();
thread1.start();
// 方式2: 实现Runnable
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
// 方式3: Lambda表达式
Thread thread3 = new Thread(() -> {
System.out.println("Lambda thread");
});
thread3.start();
// 方式4: 实现Callable(有返回值)
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
System.out.println("Callable result: " + futureTask.get());
// 方式5: 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> System.out.println("Thread pool task"));
executor.shutdown();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread running");
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable running");
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Callable completed";
}
}
17. synchronized和Lock的区别?
答案:
- synchronized: 关键字,自动释放锁,不可中断
- Lock: 接口,手动释放,可中断,更灵活
示例代码:
import java.util.concurrent.locks.*;
public class LockDemo {
private int count = 0;
private final Object lock = new Object();
private final Lock reentrantLock = new ReentrantLock();
// synchronized方法
public synchronized void incrementSync() {
count++;
}
// synchronized代码块
public void incrementBlock() {
synchronized (lock) {
count++;
}
}
// ReentrantLock
public void incrementLock() {
reentrantLock.lock();
try {
count++;
} finally {
reentrantLock.unlock(); // 必须手动释放
}
}
// 尝试获取锁
public void tryLockDemo() {
if (reentrantLock.tryLock()) {
try {
System.out.println("Lock acquired");
count++;
} finally {
reentrantLock.unlock();
}
} else {
System.out.println("Lock not available");
}
}
// 读写锁
static class ReadWriteDemo {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private int value = 0;
public int read() {
rwLock.readLock().lock();
try {
return value;
} finally {
rwLock.readLock().unlock();
}
}
public void write(int newValue) {
rwLock.writeLock().lock();
try {
value = newValue;
} finally {
rwLock.writeLock().unlock();
}
}
}
}
18. volatile关键字的作用?
答案:
- 保证可见性:一个线程修改,其他线程立即可见
- 禁止指令重排序
- 不保证原子性
示例代码:
public class VolatileDemo {
private volatile boolean flag = false;
private int count = 0;
// 可见性示例
public void visibilityDemo() {
new Thread(() -> {
while (!flag) {
// 等待flag变为true
}
System.out.println("Flag is true!");
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true; // volatile保证其他线程立即可见
}
// volatile不保证原子性
public void atomicityDemo() throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++; // 非原子操作
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Count: " + count); // 可能小于10000
}
// 单例模式 - 双重检查锁定
static class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
}
19. 线程池的核心参数和工作原理?
答案: 核心参数:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲线程存活时间
- workQueue:任务队列
- threadFactory:线程工厂
- handler:拒绝策略
示例代码:
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 20; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by " +
Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 监控线程池
System.out.println("Active threads: " + executor.getActiveCount());
System.out.println("Pool size: " + executor.getPoolSize());
System.out.println("Queue size: " + executor.getQueue().size());
executor.shutdown();
// 常用线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
ExecutorService cachedPool = Executors.newCachedThreadPool();
ExecutorService singlePool = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
// 定时任务
scheduledPool.scheduleAtFixedRate(() -> {
System.out.println("Periodic task");
}, 0, 5, TimeUnit.SECONDS);
}
}
20. 死锁的条件和如何避免?
答案: 死锁四个必要条件:
- 互斥条件
- 请求和保持条件
- 不可剥夺条件
- 循环等待条件
避免方法:
- 按顺序获取锁
- 使用tryLock超时
- 死锁检测
示例代码:
import java.util.concurrent.locks.*;
public class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
// 死锁示例
public static void deadlockExample() {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: holding lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 1: waiting for lock2");
synchronized (lock2) {
System.out.println("Thread 1: acquired lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: holding lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 2: waiting for lock1");
synchronized (lock1) {
System.out.println("Thread 2: acquired lock1");
}
}
});
t1.start();
t2.start();
}
// 避免死锁 - 按顺序获取锁
public static void avoidDeadlock() {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: holding lock1");
synchronized (lock2) {
System.out.println("Thread 1: acquired lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock1) { // 相同顺序
System.out.println("Thread 2: holding lock1");
synchronized (lock2) {
System.out.println("Thread 2: acquired lock2");
}
}
});
t1.start();
t2.start();
}
// 使用tryLock避免死锁
static class TryLockExample {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public void transfer() {
while (true) {
if (lock1.tryLock()) {
try {
if (lock2.tryLock()) {
try {
// 执行转账操作
System.out.println("Transfer completed");
return;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
// 重试
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
21. ThreadLocal的原理和应用场景?
答案: ThreadLocal为每个线程提供独立的变量副本,避免线程安全问题。
应用场景:
- 数据库连接管理
- Session管理
- 日期格式化
- 用户身份信息传递
示例代码:
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThreadLocalDemo {
// ThreadLocal变量
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
// 日期格式化(SimpleDateFormat非线程安全)
private static ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
// 基本使用
Thread t1 = new Thread(() -> {
threadLocal.set(100);
System.out.println("Thread 1: " + threadLocal.get());
threadLocal.remove(); // 避免内存泄漏
});
Thread t2 = new Thread(() -> {
threadLocal.set(200);
System.out.println("Thread 2: " + threadLocal.get());
threadLocal.remove();
});
t1.start();
t2.start();
// 日期格式化应用
for (int i = 0; i < 3; i++) {
new Thread(() -> {
String formatted = dateFormat.get().format(new Date());
System.out.println(Thread.currentThread().getName() + ": " + formatted);
}).start();
}
}
// 用户上下文示例
static class UserContext {
private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void setUser(User user) {
userThreadLocal.set(user);
}
public static User getUser() {
return userThreadLocal.get();
}
public static void clear() {
userThreadLocal.remove();
}
}
static class User {
private String username;
public User(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
}
22. CountDownLatch、CyclicBarrier、Semaphore的区别?
答案:
- CountDownLatch: 倒计时门闩,一次性,等待N个线程完成
- CyclicBarrier: 循环栅栏,可重用,等待N个线程到达屏障点
- Semaphore: 信号量,控制同时访问资源的线程数
示例代码:
import java.util.concurrent.*;
public class ConcurrencyUtils {
// CountDownLatch示例
public static void countDownLatchDemo() throws InterruptedException {
int threadCount = 5;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int id = i;
new Thread(() -> {
System.out.println("Thread " + id + " working");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + id + " finished");
latch.countDown();
}).start();
}
latch.await(); // 等待所有线程完成
System.out.println("All threads completed!");
}
// CyclicBarrier示例
public static void cyclicBarrierDemo() {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("All threads reached barrier!");
});
for (int i = 0; i < threadCount; i++) {
final int id = i;
new Thread(() -> {
try {
System.out.println("Thread " + id + " working");
Thread.sleep(1000);
System.out.println("Thread " + id + " waiting at barrier");
barrier.await(); // 等待其他线程
System.out.println("Thread " + id + " continues");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
// Semaphore示例
public static void semaphoreDemo() {
Semaphore semaphore = new Semaphore(3); // 最多3个线程同时访问
for (int i = 0; i < 10; i++) {
final int id = i;
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println("Thread " + id + " is accessing resource");
Thread.sleep(2000);
System.out.println("Thread " + id + " released resource");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}).start();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== CountDownLatch ===");
countDownLatchDemo();
Thread.sleep(3000);
System.out.println("\n=== CyclicBarrier ===");
cyclicBarrierDemo();
Thread.sleep(3000);
System.out.println("\n=== Semaphore ===");
semaphoreDemo();
}
}
23. CAS(Compare and Swap)原理?
答案: CAS是一种乐观锁机制,包含三个操作数:内存位置V、预期原值A、新值B。只有当V的值等于A时,才会将V更新为B。
示例代码:
import java.util.concurrent.atomic.*;
public class CASDemo {
private AtomicInteger count = new AtomicInteger(0);
// 使用CAS实现原子操作
public void increment() {
count.incrementAndGet(); // 内部使用CAS
}
// 手动CAS操作
public void casIncrement() {
int oldValue, newValue;
do {
oldValue = count.get();
newValue = oldValue + 1;
} while (!count.compareAndSet(oldValue, newValue));
}
// 常用Atomic类
public static void atomicDemo() {
// AtomicInteger
AtomicInteger atomicInt = new AtomicInteger(0);
System.out.println(atomicInt.incrementAndGet()); // 1
System.out.println(atomicInt.addAndGet(5)); // 6
System.out.println(atomicInt.compareAndSet(6, 10)); // true
// AtomicLong
AtomicLong atomicLong = new AtomicLong(0);
atomicLong.incrementAndGet();
// AtomicBoolean
AtomicBoolean flag = new AtomicBoolean(false);
flag.compareAndSet(false, true);
// AtomicReference
AtomicReference<String> ref = new AtomicReference<>("Hello");
ref.compareAndSet("Hello", "World");
System.out.println(ref.get()); // World
// LongAdder(高并发下性能更好)
LongAdder adder = new LongAdder();
adder.increment();
adder.add(10);
System.out.println(adder.sum());
}
// 自己实现简单的CAS
static class SimpleCAS {
private volatile int value;
public synchronized boolean compareAndSwap(int expectedValue, int newValue) {
if (value == expectedValue) {
value = newValue;
return true;
}
return false;
}
public int getValue() {
return value;
}
}
public static void main(String[] args) throws InterruptedException {
CASDemo demo = new CASDemo();
// 并发测试
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 100; j++) {
demo.increment();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Count: " + demo.count.get()); // 10000
atomicDemo();
}
}
四、Spring框架(4道)
24. Spring IoC容器的原理?
答案: IoC(控制反转)将对象的创建和依赖关系的管理交给Spring容器。通过依赖注入(DI)实现。
示例代码:
import org.springframework.beans.factory.annotation.*;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.*;
// 配置类
@Configuration
@ComponentScan("com.example")
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
// Service类
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 构造器注入(推荐)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findById(Long id) {
return userRepository.findById(id);
}
}
// Repository类
@Repository
public class UserRepository {
public User findById(Long id) {
// 数据库查询逻辑
return new User(id, "John");
}
}
// 实体类
class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
// getters and setters
}
// 使用
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
User user = userService.findById(1L);
System.out.println(user.getName());
}
}
25. Spring AOP的原理和应用?
答案: AOP(面向切面编程)通过动态代理实现横切关注点的分离。Spring AOP使用JDK动态代理或CGLIB代理。
示例代码:
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.*;
import org.springframework.stereotype.Component;
// 切面类
@Aspect
@Component
public class LoggingAspect {
// 定义切点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 前置通知
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
// 后置通知
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
// 返回通知
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("Method returned: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "serviceMethods()", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("Method threw: " + error);
}
// 环绕通知
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("Around before: " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("Around after: " + joinPoint.getSignature().getName());
long elapsedTime = System.currentTimeMillis() - start;
System.out.println("Execution time: " + elapsedTime + "ms");
return result;
}
}
// 业务类
@Service
public class OrderService {
public Order createOrder(String product, double price) {
System.out.println("Creating order...");
return new Order(product, price);
}
}
class Order {
private String product;
private double price;
public Order(String product, double price) {
this.product = product;
this.price = price;
}
}
26. Spring Bean的生命周期?
答案:
- 实例化(Instantiation)
- 属性赋值(Populate)
- 初始化(Initialization)
- 使用
- 销毁(Destruction)
示例代码:
import org.springframework.beans.factory.*;
import org.springframework.context.*;
import org.springframework.stereotype.Component;
import javax.annotation.*;
@Component
public class LifeCycleBean implements InitializingBean, DisposableBean,
BeanNameAware, BeanFactoryAware, ApplicationContextAware {
private String name;
// 1. 构造器
public LifeCycleBean() {
System.out.println("1. Constructor called");
}
// 2. 属性注入
@Autowired
public void setName(String name) {
this.name = name;
System.out.println("2. Properties set");
}
// 3. BeanNameAware
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware: " + name);
}
// 4. BeanFactoryAware
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4. BeanFactoryAware called");
}
// 5. ApplicationContextAware
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
System.out.println("5. ApplicationContextAware called");
}
// 6. @PostConstruct
@PostConstruct
public void postConstruct() {
System.out.println("6. @PostConstruct called");
}
// 7. InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("7. InitializingBean.afterPropertiesSet called");
}
// 8. 自定义初始化方法
@Bean(initMethod = "customInit")
public void customInit() {
System.out.println("8. Custom init method called");
}
// 9. @PreDestroy
@PreDestroy
public void preDestroy() {
System.out.println("9. @PreDestroy called");
}
// 10. DisposableBean
@Override
public void destroy() throws Exception {
System.out.println("10. DisposableBean.destroy called");
}
}
27. Spring的事务管理?
答案: Spring支持编程式和声明式事务管理。声明式事务通过@Transactional注解实现。
事务传播行为:
- REQUIRED(默认):有事务则加入,无则创建
- REQUIRES_NEW:总是创建新事务
- NESTED:嵌套事务
- SUPPORTS:有事务则加入,无则非事务
- NOT_SUPPORTED:非事务执行
- NEVER:不能有事务
- MANDATORY:必须有事务
示例代码:
import org.springframework.transaction.annotation.*;
import org.springframework.stereotype.Service;
@Service
public class TransactionService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentRepository paymentRepository;
// 声明式事务
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
paymentRepository.createPayment(order.getId());
// 如果抛异常,事务会回滚
}
// 配置事务属性
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
rollbackFor = Exception.class
)
public void complexTransaction() {
// 业务逻辑
}
// 只读事务
@Transactional(readOnly = true)
public List<Order> findAllOrders() {
return orderRepository.findAll();
}
// 事务传播
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void independentTransaction() {
// 总是在新事务中执行
}
// 编程式事务
@Autowired
private TransactionTemplate transactionTemplate;
public void programmaticTransaction() {
transactionTemplate.execute(status -> {
try {
orderRepository.save(new Order());
paymentRepository.createPayment(1L);
return null;
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
}
}
// 事务配置
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
五、JVM与性能优化(3道)
28. JVM内存结构?
答案: JVM内存分为:
- 堆(Heap) : 存放对象实例,GC主要区域
- 年轻代(Young Generation):Eden + 2个Survivor
- 老年代(Old Generation)
- 方法区(Method Area/Metaspace): 类信息、常量池、静态变量
- 栈(Stack): 线程私有,存放局部变量、方法调用
- 程序计数器(PC Register): 当前线程执行字节码的行号
- 本地方法栈(Native Method Stack): native方法
示例代码:
public class JVMMemoryDemo {
// 类变量 - 方法区
private static int staticVar = 100;
// 实例变量 - 堆
private int instanceVar = 200;
// 常量池 - 方法区
private static final String CONSTANT = "Hello";
public void method() {
// 局部变量 - 栈
int localVar = 300;
// 对象 - 堆
Object obj = new Object();
// 数组 - 堆
int[] array = new int[10];
}
// 查看JVM内存使用
public static void printMemoryInfo() {
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory(); // 最大内存
long totalMemory = runtime.totalMemory(); // 总内存
long freeMemory = runtime.freeMemory(); // 空闲内存
System.out.println("Max Memory: " + maxMemory / 1024 / 1024 + "MB");
System.out.println("Total Memory: " + totalMemory / 1024 / 1024 + "MB");
System.out.println("Free Memory: " + freeMemory / 1024 / 1024 + "MB");
System.out.println("Used Memory: " + (totalMemory - freeMemory) / 1024 / 1024 + "MB");
}
public static void main(String[] args) {
printMemoryInfo();
// JVM参数示例
// -Xms512m 初始堆大小
// -Xmx1024m 最大堆大小
// -Xss256k 线程栈大小
// -XX:MetaspaceSize=128m 元空间初始大小
// -XX:MaxMetaspaceSize=256m 元空间最大大小
}
}
29. 垃圾回收算法和垃圾收集器?
答案: GC算法:
- 标记-清除(Mark-Sweep): 标记存活对象,清除未标记对象
- 标记-复制(Mark-Copy): 复制存活对象到另一区域
- 标记-整理(Mark-Compact): 标记后整理,移动存活对象
垃圾收集器:
- Serial GC: 单线程
- Parallel GC: 多线程,吞吐量优先
- CMS: 并发标记清除,低延迟
- G1 GC: 分区收集,可预测停顿
- ZGC: 低延迟(<10ms)
示例代码:
public class GCDemo {
// 触发GC
public static void triggerGC() {
System.gc(); // 建议JVM执行GC
// 注意:System.gc()只是建议,JVM可能不执行
}
// 对象可达性分析
static class GCRootDemo {
private static Object staticReference; // GC Root
public void method() {
Object localReference = new Object(); // GC Root(活跃)
new Thread(() -> {
Object threadReference = new Object(); // GC Root(线程)
}).start();
}
}
// 观察GC
public static void observeGC() {
List<byte[]> list = new ArrayList<>();
try {
while (true) {
// 分配1MB内存
byte[] array = new byte[1024 * 1024];
list.add(array);
// 每100次输出一次
if (list.size() % 100 == 0) {
System.out.println("Allocated: " + list.size() + "MB");
}
}
} catch (OutOfMemoryError e) {
System.out.println("Out of memory!");
}
}
// 引用类型与GC
public static void referenceTypes() {
// 强引用
Object strong = new Object();
// 软引用 - 内存不足时回收
SoftReference<Object> soft = new SoftReference<>(new Object());
// 弱引用 - GC时回收
WeakReference<Object> weak = new WeakReference<>(new Object());
// 虚引用 - 无法获取对象
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), queue);
// 触发GC
System.gc();
System.out.println("Soft: " + soft.get());
System.out.println("Weak: " + weak.get());
System.out.println("Phantom: " + phantom.get());
}
public static void main(String[] args) {
referenceTypes();
// JVM GC参数
// -XX:+UseSerialGC 使用Serial GC
// -XX:+UseParallelGC 使用Parallel GC
// -XX:+UseConcMarkSweepGC 使用CMS GC
// -XX:+UseG1GC 使用G1 GC
// -XX:+PrintGCDetails 打印GC详情
}
}
30. 如何进行Java性能调优?
答案: 调优步骤:
- 性能监控(JConsole、VisualVM、JProfiler)
- 代码分析(找出热点代码)
- 内存优化(减少对象创建、避免内存泄漏)
- 并发优化(合理使用线程池)
- JVM参数调优
示例代码:
import java.util.concurrent.*;
public class PerformanceOptimization {
// 1. 避免频繁创建对象
// 不好的做法
public String badConcat() {
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次都创建新String对象
}
return result;
}
// 好的做法
public String goodConcat() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
return sb.toString();
}
// 2. 使用对象池
static class ObjectPool<T> {
private final Queue<T> pool = new ConcurrentLinkedQueue<>();
private final Supplier<T> factory;
public ObjectPool(Supplier<T> factory, int initialSize) {
this.factory = factory;
for (int i = 0; i < initialSize; i++) {
pool.offer(factory.get());
}
}
public T borrow() {
T object = pool.poll();
return object != null ? object : factory.get();
}
public void returnObject(T object) {
pool.offer(object);
}
}
// 3. 延迟初始化
static class LazyInit {
private static class Holder {
static final ExpensiveObject INSTANCE = new ExpensiveObject();
}
public static ExpensiveObject getInstance() {
return Holder.INSTANCE;
}
}
static class ExpensiveObject {
public ExpensiveObject() {
// 昂贵的初始化操作
}
}
// 4. 缓存常用数据
static class Cache {
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
public Object get(String key) {
return cache.computeIfAbsent(key, k -> {
// 从数据库加载
return loadFromDatabase(k);
});
}
private Object loadFromDatabase(String key) {
// 数据库查询
return new Object();
}
}
// 5. 批处理
public void batchProcess(List<Integer> ids) {
// 不好:逐个处理
// for (Integer id : ids) {
// processOne(id);
// }
// 好:批量处理
processBatch(ids);
}
private void processBatch(List<Integer> ids) {
// 批量处理逻辑
}
// 6. 异步处理
private ExecutorService executor = Executors.newFixedThreadPool(10);
public CompletableFuture<String> asyncProcess(String data) {
return CompletableFuture.supplyAsync(() -> {
// 耗时操作
return processData(data);
}, executor);
}
private String processData(String data) {
return data.toUpperCase();
}
// 性能测试工具
public static void benchmark(Runnable task, String name) {
long start = System.nanoTime();
task.run();
long end = System.nanoTime();
System.out.println(name + " took: " + (end - start) / 1_000_000 + "ms");
}
public static void main(String[] args) {
PerformanceOptimization opt = new PerformanceOptimization();
benchmark(opt::badConcat, "Bad concat");
benchmark(opt::goodConcat, "Good concat");
}
}
// JVM调优参数示例
// -Xms2g -Xmx2g 设置堆大小
// -XX:NewRatio=2 年轻代与老年代比例
// -XX:SurvivorRatio=8 Eden与Survivor比例
// -XX:+UseG1GC 使用G1垃圾收集器
// -XX:MaxGCPauseMillis=200 最大GC停顿时间
// -XX:+HeapDumpOnOutOfMemoryError 内存溢出时dump
⚛️ React 面试题(30道)
一、React基础(10道)
31. React是什么?它的主要特点是什么?
答案: React是用于构建用户界面的JavaScript库,主要特点:
- 声明式编程:描述UI应该是什么样子
- 组件化:构建可复用的UI组件
- 虚拟DOM:提高渲染性能
- 单向数据流:数据从父组件流向子组件
- Learn Once, Write Anywhere:可用于Web、移动端等
示例代码:
import React from 'react';
// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 类组件
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
// App组件
function App() {
return (
<div>
<Welcome name="React" />
<Counter />
</div>
);
}
export default App;
32. JSX是什么?为什么使用JSX?
答案: JSX是JavaScript XML的缩写,是React的语法扩展。它让我们可以在JavaScript中编写HTML结构。
优点:
- 直观、易读
- 防止XSS攻击(自动转义)
- 支持JavaScript表达式
- 编译时错误检查
示例代码:
// JSX语法
const element = <h1>Hello World</h1>;
// 等价于
const element = React.createElement('h1', null, 'Hello World');
// JSX中的表达式
function Greeting({ name, age }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>You are {age} years old</p>
{age >= 18 ? <p>Adult</p> : <p>Minor</p>}
</div>
);
}
// JSX属性
function Image() {
const url = 'https://example.com/image.jpg';
const altText = 'Description';
return (
<img
src={url}
alt={altText}
className="responsive-image"
style={{ width: '100%', borderRadius: '8px' }}
/>
);
}
// JSX子元素
function List() {
const items = ['Apple', 'Banana', 'Cherry'];
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
// JSX片段
function Fragment() {
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
}
33. 虚拟DOM是什么?它是如何工作的?
答案: 虚拟DOM是真实DOM的轻量级JavaScript对象表示。React通过diff算法比较新旧虚拟DOM,只更新变化的部分。
工作流程:
- 状态改变时创建新的虚拟DOM树
- 对比新旧虚拟DOM(diff算法)
- 计算最小更新
- 批量更新真实DOM
示例代码:
import React, { useState } from 'react';
function VirtualDOMDemo() {
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
// 添加项目 - 只会更新新增的DOM节点
const addItem = () => {
setItems([...items, `Item ${items.length + 1}`]);
};
// 删除项目 - 只会删除对应的DOM节点
const removeItem = (index) => {
setItems(items.filter((_, i) => i !== index));
};
return (
<div>
<button onClick={addItem}>Add Item</button>
<ul>
{items.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => removeItem(index)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
// Key的重要性
function KeyExample() {
const [users, setUsers] = useState([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]);
// 不好:使用index作为key
return (
<ul>
{users.map((user, index) => (
<li key={index}>{user.name}</li>
))}
</ul>
);
// 好:使用唯一ID作为key
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default VirtualDOMDemo;
34. React组件的生命周期有哪些?
答案: 类组件生命周期:
- 挂载:constructor → getDerivedStateFromProps → render → componentDidMount
- 更新:getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate
- 卸载:componentWillUnmount
函数组件:使用useEffect Hook替代
示例代码:
import React, { Component, useEffect, useState } from 'react';
// 类组件生命周期
class LifecycleClass extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
console.log('1. constructor');
}
static getDerivedStateFromProps(props, state) {
console.log('2. getDerivedStateFromProps');
return null;
}
componentDidMount() {
console.log('4. componentDidMount');
// API调用、订阅、定时器
}
shouldComponentUpdate(nextProps, nextState) {
console.log('5. shouldComponentUpdate');
return true; // 返回false阻止更新
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('7. getSnapshotBeforeUpdate');
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('8. componentDidUpdate');
}
componentWillUnmount() {
console.log('9. componentWillUnmount');
// 清理:取消订阅、清除定时器
}
render() {
console.log('3/6. render');
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}
// 函数组件 - useEffect模拟生命周期
function LifecycleFunction() {
const [count, setCount] = useState(0);
// componentDidMount
useEffect(() => {
console.log('Component mounted');
}, []);
// componentDidUpdate(count变化时)
useEffect(() => {
console.log('Count updated:', count);
}, [count]);
// componentWillUnmount
useEffect(() => {
return () => {
console.log('Component will unmount');
};
}, []);
// 组合:监听多个状态
useEffect(() => {
console.log('Component mounted or updated');
return () => {
console.log('Cleanup');
};
});
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export { LifecycleClass, LifecycleFunction };
35. 受控组件和非受控组件的区别?
答案:
- 受控组件:表单数据由React组件的state管理
- 非受控组件:表单数据由DOM自身管理,使用ref获取
示例代码:
import React, { useState, useRef } from 'react';
// 受控组件
function ControlledForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
placeholder="Username"
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="Password"
/>
<button type="submit">Submit</button>
</form>
);
}
// 非受控组件
function UncontrolledForm() {
const usernameRef = useRef();
const emailRef = useRef();
const passwordRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
const formData = {
username: usernameRef.current.value,
email: emailRef.current.value,
password: passwordRef.current.value
};
console.log('Submitted:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={usernameRef}
defaultValue=""
placeholder="Username"
/>
<input
type="email"
ref={emailRef}
defaultValue=""
placeholder="Email"
/>
<input
type="password"
ref={passwordRef}
defaultValue=""
placeholder="Password"
/>
<button type="submit">Submit</button>
</form>
);
}
// 文件上传(非受控)
function FileUpload() {
const fileInputRef = useRef();
const handleUpload = () => {
const file = fileInputRef.current.files[0];
if (file) {
console.log('Uploading:', file.name);
// 上传逻辑
}
};
return (
<div>
<input type="file" ref={fileInputRef} />
<button onClick={handleUpload}>Upload</button>
</div>
);
}
export { ControlledForm, UncontrolledForm, FileUpload };
36. props和state的区别?
答案:
- props: 父组件传递给子组件的数据,只读,不可修改
- state: 组件内部管理的数据,可变,通过setState/useState修改
示例代码:
import React, { useState } from 'react';
// Props示例
function ChildComponent({ name, age, onUpdate }) {
// props是只读的
// name = 'New Name'; // 错误!
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
<button onClick={() => onUpdate('Updated')}>
Update Parent
</button>
</div>
);
}
// State示例
function ParentComponent() {
const [message, setMessage] = useState('Initial message');
const [user, setUser] = useState({ name: 'John', age: 25 });
const handleUpdate = (newMessage) => {
setMessage(newMessage);
};
const updateAge = () => {
setUser(prev => ({
...prev,
age: prev.age + 1
}));
};
return (
<div>
<p>Message: {message}</p>
<ChildComponent
name={user.name}
age={user.age}
onUpdate={handleUpdate}
/>
<button onClick={updateAge}>Increment Age</button>
</div>
);
}
// Props默认值
function Button({ text = 'Click Me', variant = 'primary', onClick }) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{text}
</button>
);
}
// Props类型检查(使用PropTypes)
import PropTypes from 'prop-types';
function UserCard({ user, onEdit }) {
return (
<div>
<h3>{user.name}</h3>
<p>Age: {user.age}</p>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
}
UserCard.propTypes = {
user: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
age: PropTypes.number
}).isRequired,
onEdit: PropTypes.func.isRequired
};
export default ParentComponent;
37. 事件处理在React中是如何工作的?
答案: React使用合成事件(SyntheticEvent)包装原生事件,提供跨浏览器一致性。
特点:
- 事件委托到根节点
- 自动绑定this(箭头函数或bind)
- 阻止默认行为需要显式调用preventDefault
示例代码:
import React, { useState } from 'react';
function EventHandling() {
const [clicks, setClicks] = useState(0);
// 方式1:箭头函数(推荐)
const handleClick1 = () => {
setClicks(clicks + 1);
};
// 方式2:直接在JSX中使用箭头函数
// 注意:每次渲染都会创建新函数
// 方式3:传递参数
const handleClickWithParam = (id) => {
console.log('Clicked item:', id);
};
// 阻止默认行为
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted');
};
// 阻止事件冒泡
const handleInnerClick = (e) => {
e.stopPropagation();
console.log('Inner clicked');
};
const handleOuterClick = () => {
console.log('Outer clicked');
};
// 获取事件对象
const handleInput = (e) => {
console.log('Value:', e.target.value);
console.log('Event type:', e.type);
console.log('Native event:', e.nativeEvent);
};
return (
<div>
{/* 方式1 */}
<button onClick={handleClick1}>
Clicks: {clicks}
</button>
{/* 方式2 */}
<button onClick={() => setClicks(clicks + 1)}>
Increment
</button>
{/* 传递参数 */}
<button onClick={() => handleClickWithParam(123)}>
Click with ID
</button>
{/* 阻止默认行为 */}
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
{/* 事件冒泡 */}
<div onClick={handleOuterClick} style={{ padding: '20px', background: '#eee' }}>
Outer
<button onClick={handleInnerClick}>Inner</button>
</div>
{/* 各种事件 */}
<input
type="text"
onChange={handleInput}
onFocus={() => console.log('Focused')}
onBlur={() => console.log('Blurred')}
onKeyDown={(e) => {
if (e.key === 'Enter') {
console.log('Enter pressed');
}
}}
/>
{/* 鼠标事件 */}
<div
onMouseEnter={() => console.log('Mouse entered')}
onMouseLeave={() => console.log('Mouse left')}
onMouseMove={(e) => console.log('Mouse at:', e.clientX, e.clientY)}
>
Hover me
</div>
</div>
);
}
// 类组件中的事件处理
class ClassEventHandling extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
// 方式3:构造器中bind
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
// 方式4:类属性(箭头函数)
handleClick2 = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<button onClick={this.handleClick}>
Count: {this.state.count}
</button>
<button onClick={this.handleClick2}>
Increment
</button>
</div>
);
}
}
export default EventHandling;
38. 如何在React中进行条件渲染?
答案: React支持多种条件渲染方式:if语句、三元运算符、逻辑与&&、switch语句等。
示例代码:
import React, { useState } from 'react';
function ConditionalRendering() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userRole, setUserRole] = useState('guest');
const [items, setItems] = useState([]);
// 方式1:if-else语句
function renderGreeting() {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
} else {
return <h1>Please sign in</h1>;
}
}
// 方式2:三元运算符
const greeting = (
<div>
{isLoggedIn ? (
<h1>Welcome back!</h1>
) : (
<h1>Please sign in</h1>
)}
</div>
);
// 方式3:逻辑与 &&
const notification = (
<div>
{isLoggedIn && <p>You have new messages</p>}
{!isLoggedIn && <p>Please log in to see messages</p>}
</div>
);
// 方式4:立即执行函数
const complexCondition = (
<div>
{(() => {
if (userRole === 'admin') {
return <AdminPanel />;
} else if (userRole === 'user') {
return <UserPanel />;
} else {
return <GuestPanel />;
}
})()}
</div>
);
// 方式5:switch语句
function renderByRole() {
switch (userRole) {
case 'admin':
return <AdminPanel />;
case 'user':
return <UserPanel />;
case 'guest':
default:
return <GuestPanel />;
}
}
// 方式6:元素变量
let content;
if (isLoggedIn) {
content = <Dashboard />;
} else {
content = <LoginForm />;
}
// 方式7:空列表检查
const listView = (
<div>
{items.length > 0 ? (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
) : (
<p>No items found</p>
)}
</div>
);
// 方式8:多条件
const multiCondition = (
<div>
{isLoggedIn && userRole === 'admin' && <AdminSettings />}
</div>
);
// 方式9:防止渲染
if (!isLoggedIn) {
return null; // 或 return <div>Loading...</div>
}
return (
<div>
{renderGreeting()}
{greeting}
{notification}
{complexCondition}
{renderByRole()}
{content}
{listView}
{multiCondition}
<button onClick={() => setIsLoggedIn(!isLoggedIn)}>
Toggle Login
</button>
<select onChange={(e) => setUserRole(e.target.value)} value={userRole}>
<option value="guest">Guest</option>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
);
}
// 辅助组件
const AdminPanel = () => <div>Admin Panel</div>;
const UserPanel = () => <div>User Panel</div>;
const GuestPanel = () => <div>Guest Panel</div>;
const Dashboard = () => <div>Dashboard</div>;
const LoginForm = () => <div>Login Form</div>;
const AdminSettings = () => <div>Admin Settings</div>;
export default ConditionalRendering;
39. React中的列表和Keys?
答案: 在React中渲染列表时,每个列表项需要唯一的key来帮助React识别哪些项改变了。
Key的要求:
- 在兄弟节点中唯一
- 稳定、可预测、不变
- 不要使用index(除非列表静态且不会重排序)
示例代码:
import React, { useState } from 'react';
function ListsAndKeys() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', completed: false },
{ id: 2, text: 'Build a project', completed: false },
{ id: 3, text: 'Deploy', completed: true }
]);
const [nextId, setNextId] = useState(4);
// 添加待办事项
const addTodo = (text) => {
setTodos([...todos, { id: nextId, text, completed: false }]);
setNextId(nextId + 1);
};
// 删除待办事项
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// 切换完成状态
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
return (
<div>
<h2>Todo List</h2>
{/* 基本列表渲染 */}
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
{/* 添加新项 */}
<button onClick={() => addTodo('New task')}>Add Todo</button>
</div>
);
}
// 嵌套列表
function NestedList() {
const categories = [
{
id: 1,
name: 'Fruits',
items: [
{ id: 11, name: 'Apple' },
{ id: 12, name: 'Banana' }
]
},
{
id: 2,
name: 'Vegetables',
items: [
{ id: 21, name: 'Carrot' },
{ id: 22, name: 'Broccoli' }
]
}
];
return (
<div>
{categories.map(category => (
<div key={category.id}>
<h3>{category.name}</h3>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
</div>
);
}
// 提取列表组件
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={onToggle}
/>
<span>{todo.text}</span>
<button onClick={onDelete}>Delete</button>
</li>
);
}
function TodoListRefactored() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Task 1', completed: false },
{ id: 2, text: 'Task 2', completed: true }
]);
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={() => console.log('Toggle', todo.id)}
onDelete={() => console.log('Delete', todo.id)}
/>
))}
</ul>
);
}
// 不好的做法:使用index作为key
function BadKeyExample() {
const [items, setItems] = useState(['A', 'B', 'C']);
// 不好:使用index
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li> // ❌ 问题:重新排序时会出错
))}
</ul>
);
}
export default ListsAndKeys;
40. React中的Fragments是什么?
答案: Fragments让你可以将子列表分组,而无需向DOM添加额外节点。
使用场景:
- 返回多个元素
- 避免额外的div包装
- 表格行、列表项等
示例代码:
import React from 'react';
// 方式1:React.Fragment
function LongForm() {
return (
<React.Fragment>
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</React.Fragment>
);
}
// 方式2:短语法 <>...</>
function ShortForm() {
return (
<>
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</>
);
}
// 带key的Fragment(不能使用短语法)
function ListWithFragments() {
const items = [
{ id: 1, term: 'React', description: 'A JavaScript library' },
{ id: 2, term: 'Component', description: 'A reusable piece of UI' }
];
return (
<dl>
{items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
// 表格中使用Fragment
function TableRows() {
return (
<table>
<tbody>
<tr>
<Columns />
</tr>
</tbody>
</table>
);
}
function Columns() {
return (
<>
<td>Column 1</td>
<td>Column 2</td>
<td>Column 3</td>
</>
);
}
// 条件Fragment
function ConditionalFragment({ showDetails }) {
return (
<div>
<h1>Title</h1>
{showDetails && (
<>
<p>Detail 1</p>
<p>Detail 2</p>
<p>Detail 3</p>
</>
)}
</div>
);
}
export default ShortForm;
二、React Hooks(10道)
41. useState Hook的使用?
答案: useState是React中最基本的Hook,用于在函数组件中添加状态。
示例代码:
import React, { useState } from 'react';
function UseStateExamples() {
// 基本用法
const [count, setCount] = useState(0);
// 多个状态
const [name, setName] = useState('John');
const [age, setAge] = useState(25);
// 对象状态
const [user, setUser] = useState({
name: 'Alice',
email: 'alice@example.com',
age: 30
});
// 数组状态
const [items, setItems] = useState(['Apple', 'Banana']);
// 布尔状态
const [isLoading, setIsLoading] = useState(false);
const [isVisible, setIsVisible] = useState(true);
// 函数式更新(基于前一个状态)
const increment = () => {
setCount(prevCount => prevCount + 1);
};
const decrement = () => {
setCount(prevCount => prevCount - 1);
};
// 更新对象状态
const updateUser = () => {
setUser(prevUser => ({
...prevUser,
age: prevUser.age + 1
}));
};
// 更新数组状态
const addItem = () => {
setItems(prevItems => [...prevItems, `Item ${prevItems.length + 1}`]);
};
const removeItem = (index) => {
setItems(prevItems => prevItems.filter((_, i) => i !== index));
};
// 延迟初始化(性能优化)
const [expensiveValue, setExpensiveValue] = useState(() => {
console.log('Computing expensive value...');
return computeExpensiveValue();
});
function computeExpensiveValue() {
// 昂贵的计算
return Math.random() * 1000;
}
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={decrement}>Decrement (functional)</button>
<h2>User Info</h2>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Age: {user.age}</p>
<button onClick={updateUser}>Increase Age</button>
<h2>Items</h2>
<ul>
{items.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => removeItem(index)}>Remove</button>
</li>
))}
</ul>
<button onClick={addItem}>Add Item</button>
<h2>Toggle</h2>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? 'Hide' : 'Show'}
</button>
{isVisible && <p>This content is visible</p>}
<p>Expensive Value: {expensiveValue}</p>
</div>
);
}
// 表单管理
function FormWithState() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
terms: false
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={formData.username}
onChange={handleChange}
placeholder="Username"
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<input
name="password"
type="password"
value={formData.password}
onChange={handleChange}
placeholder="Password"
/>
<label>
<input
name="terms"
type="checkbox"
checked={formData.terms}
onChange={handleChange}
/>
Accept Terms
</label>
<button type="submit">Submit</button>
</form>
);
}
export default UseStateExamples;
42. useEffect Hook的使用?
答案: useEffect用于处理副作用,如数据获取、订阅、DOM操作等。
示例代码:
import React, { useState, useEffect } from 'react';
function UseEffectExamples() {
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
const [isOnline, setIsOnline] = useState(navigator.onLine);
// 1. 每次渲染后执行
useEffect(() => {
console.log('Component rendered');
});
// 2. 仅在mount时执行(componentDidMount)
useEffect(() => {
console.log('Component mounted');
document.title = 'React App';
}, []);
// 3. 依赖特定值(count变化时执行)
useEffect(() => {
console.log('Count changed:', count);
document.title = `Count: ${count}`;
}, [count]);
// 4. 清理函数(componentWillUnmount)
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
// 清理函数
return () => {
clearInterval(timer);
console.log('Timer cleaned up');
};
}, []);
// 5. 数据获取
useEffect(() => {
let isMounted = true;
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
if (isMounted) {
setData(result);
}
} catch (error) {
console.error('Error fetching data:', error);
}
}
fetchData();
return () => {
isMounted = false;
};
}, []);
// 6. 订阅/取消订阅
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
// 7. 多个依赖
useEffect(() => {
console.log('Count or data changed');
}, [count, data]);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p>Status: {isOnline ? 'Online' : 'Offline'}</p>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}
// localStorage同步
function LocalStorageSync() {
const [name, setName] = useState(() => {
return localStorage.getItem('name') || '';
});
useEffect(() => {
localStorage.setItem('name', name);
}, [name]);
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
);
}
// 窗口大小监听
function WindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div>
<p>Width: {size.width}px</p>
<p>Height: {size.height}px</p>
</div>
);
}
export { UseEffectExamples, LocalStorageSync, WindowSize };
43. useContext Hook的使用?
答案: useContext用于访问React Context,避免props drilling。
示例代码:
import React, { createContext, useContext, useState } from 'react';
// 创建Context
const ThemeContext = createContext();
const UserContext = createContext();
const LanguageContext = createContext();
// Theme Provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// User Provider
function UserProvider({ children }) {
const [user, setUser] = useState({ name: 'John', role: 'user' });
const login = (userData) => {
setUser(userData);
};
const logout = () => {
setUser(null);
};
return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
}
// 使用Context
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff'
}}
>
Toggle Theme (Current: {theme})
</button>
);
}
function UserProfile() {
const { user, logout } = useContext(UserContext);
if (!user) {
return <p>Please log in</p>;
}
return (
<div>
<h3>Welcome, {user.name}!</h3>
<p>Role: {user.role}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
// 多个Context
function MultiContextComponent() {
const { theme } = useContext(ThemeContext);
const { user } = useContext(UserContext);
return (
<div style={{
background: theme === 'light' ? '#f0f0f0' : '#222',
color: theme === 'light' ? '#000' : '#fff',
padding: '20px'
}}>
<p>Theme: {theme}</p>
<p>User: {user?.name || 'Guest'}</p>
</div>
);
}
// 自定义Hook封装Context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
}
// App
function App() {
return (
<ThemeProvider>
<UserProvider>
<div>
<ThemedButton />
<UserProfile />
<MultiContextComponent />
</div>
</UserProvider>
</ThemeProvider>
);
}
export default App;
44. useReducer Hook的使用?
答案: useReducer是useState的替代方案,适合复杂的状态逻辑。
示例代码:
import React, { useReducer } from 'react';
// 简单计数器
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
case 'SET':
return { count: action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<h2>Count: {state.count}</h2>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
<button onClick={() => dispatch({ type: 'SET', payload: 100 })}>
Set to 100
</button>
</div>
);
}
// Todo列表
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, {
id: Date.now(),
text: action.payload,
completed: false
}]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case 'SET_FILTER':
return {
...state,
filter: action.payload
};
default:
return state;
}
};
function TodoApp() {
const initialState = {
todos: [],
filter: 'all'
};
const [state, dispatch] = useReducer(todoReducer, initialState);
const [input, setInput] = React.useState('');
const addTodo = () => {
if (input.trim()) {
dispatch({ type: 'ADD_TODO', payload: input });
setInput('');
}
};
const filteredTodos = state.todos.filter(todo => {
if (state.filter === 'active') return !todo.completed;
if (state.filter === 'completed') return todo.completed;
return true;
});
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo"
/>
<button onClick={addTodo}>Add</button>
<div>
<button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'all' })}>
All
</button>
<button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'active' })}>
Active
</button>
<button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'completed' })}>
Completed
</button>
</div>
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
// 表单管理
const formReducer = (state, action) => {
switch (action.type) {
case 'UPDATE_FIELD':
return {
...state,
[action.field]: action.value
};
case 'RESET_FORM':
return action.payload;
default:
return state;
}
};
function Form() {
const initialState = {
username: '',
email: '',
password: ''
};
const [state, dispatch] = useReducer(formReducer, initialState);
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form data:', state);
dispatch({ type: 'RESET_FORM', payload: initialState });
};
return (
<form onSubmit={handleSubmit}>
<input
value={state.username}
onChange={(e) => dispatch({
type: 'UPDATE_FIELD',
field: 'username',
value: e.target.value
})}
placeholder="Username"
/>
<input
value={state.email}
onChange={(e) => dispatch({
type: 'UPDATE_FIELD',
field: 'email',
value: e.target.value
})}
placeholder="Email"
/>
<input
type="password"
value={state.password}
onChange={(e) => dispatch({
type: 'UPDATE_FIELD',
field: 'password',
value: e.target.value
})}
placeholder="Password"
/>
<button type="submit">Submit</button>
</form>
);
}
export { Counter, TodoApp, Form };
45. useRef Hook的使用?
答案: useRef用于访问DOM元素或保存可变值而不触发重新渲染。
示例代码:
import React, { useRef, useState, useEffect } from 'react';
function UseRefExamples() {
const inputRef = useRef(null);
const countRef = useRef(0);
const [renderCount, setRenderCount] = useState(0);
// 1. 访问DOM元素
const focusInput = () => {
inputRef.current.focus();
};
const clearInput = () => {
inputRef.current.value = '';
};
// 2. 保存可变值(不触发重新渲染)
useEffect(() => {
countRef.current = countRef.current + 1;
console.log('Render count:', countRef.current);
});
return (
<div>
<input ref={inputRef} type="text" placeholder="Enter text" />
<button onClick={focusInput}>Focus Input</button>
<button onClick={clearInput}>Clear Input</button>
<button onClick={() => setRenderCount(renderCount + 1)}>
Re-render
</button>
<p>Component rendered {countRef.current} times</p>
</div>
);
}
// 前一个值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function PreviousValue() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>Current: {count}</p>
<p>Previous: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// 计时器
function Timer() {
const [time, setTime] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef(null);
useEffect(() => {
if (isRunning) {
intervalRef.current = setInterval(() => {
setTime(t => t + 1);
}, 1000);
} else {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [isRunning]);
return (
<div>
<p>Time: {time}s</p>
<button onClick={() => setIsRunning(!isRunning)}>
{isRunning ? 'Pause' : 'Start'}
</button>
<button onClick={() => setTime(0)}>Reset</button>
</div>
);
}
// 视频播放器
function VideoPlayer() {
const videoRef = useRef(null);
const play = () => {
videoRef.current.play();
};
const pause = () => {
videoRef.current.pause();
};
const seek = (time) => {
videoRef.current.currentTime = time;
};
return (
<div>
<video ref={videoRef} width="400" src="video.mp4" />
<button onClick={play}>Play</button>
<button onClick={pause}>Pause</button>
<button onClick={() => seek(0)}>Restart</button>
</div>
);
}
// 滚动到元素
function ScrollToElement() {
const sectionRef = useRef(null);
const scrollToSection = () => {
sectionRef.current.scrollIntoView({ behavior: 'smooth' });
};
return (
<div>
<button onClick={scrollToSection}>Scroll to Section</button>
<div style={{ height: '1000px' }}>Spacer</div>
<section ref={sectionRef}>
<h2>Target Section</h2>
</section>
</div>
);
}
export { UseRefExamples, PreviousValue, Timer, VideoPlayer, ScrollToElement };
46. useMemo和useCallback的区别和使用?
答案:
- useMemo: 缓存计算结果
- useCallback: 缓存函数引用
两者都用于性能优化,避免不必要的重新计算或重新渲染。
示例代码:
import React, { useState, useMemo, useCallback } from 'react';
// useMemo示例
function UseMemoExample() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// 昂贵的计算 - 使用useMemo缓存
const expensiveCalculation = useMemo(() => {
console.log('Computing expensive value...');
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i;
}
return result;
}, [count]); // 只在count变化时重新计算
// 过滤列表 - 使用useMemo
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.active);
}, [items]);
return (
<div>
<p>Count: {count}</p>
<p>Expensive result: {expensiveCalculation}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p>Filtered items: {filteredItems.length}</p>
</div>
);
}
// useCallback示例
const ChildComponent = React.memo(({ onButtonClick }) => {
console.log('Child rendered');
return <button onClick={onButtonClick}>Click Me</button>;
});
function UseCallbackExample() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(0);
// 不使用useCallback - 每次渲染都创建新函数
const handleClickBad = () => {
console.log('Clicked');
};
// 使用useCallback - 缓存函数引用
const handleClickGood = useCallback(() => {
console.log('Clicked with count:', count);
}, [count]); // 只在count变化时创建新函数
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<button onClick={() => setOtherState(otherState + 1)}>
Update Other State
</button>
<ChildComponent onButtonClick={handleClickGood} />
</div>
);
}
// 实际应用:搜索和排序
function SearchAndSort() {
const [query, setQuery] = useState('');
const [sortOrder, setSortOrder] = useState('asc');
const [data] = useState([
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 20 }
]);
// 搜索过滤
const filteredData = useMemo(() => {
console.log('Filtering...');
return data.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}, [data, query]);
// 排序
const sortedData = useMemo(() => {
console.log('Sorting...');
return [...filteredData].sort((a, b) => {
if (sortOrder === 'asc') {
return a.age - b.age;
}
return b.age - a.age;
});
}, [filteredData, sortOrder]);
// 缓存事件处理器
const handleSearch = useCallback((e) => {
setQuery(e.target.value);
}, []);
const handleSort = useCallback(() => {
setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
}, []);
return (
<div>
<input
type="text"
value={query}
onChange={handleSearch}
placeholder="Search by name"
/>
<button onClick={handleSort}>
Sort by Age ({sortOrder})
</button>
<ul>
{sortedData.map(item => (
<li key={item.id}>
{item.name} - {item.age}
</li>
))}
</ul>
</div>
);
}
// 何时使用
function WhenToUse() {
const [count, setCount] = useState(0);
// ❌ 不需要useMemo的情况
const simpleValue = count * 2; // 简单计算,不需要缓存
// ✅ 需要useMemo的情况
const expensiveValue = useMemo(() => {
// 昂贵的计算
return heavyComputation(count);
}, [count]);
// ❌ 不需要useCallback的情况(没有作为props传递)
const handleClick1 = () => {
setCount(count + 1);
};
// ✅ 需要useCallback的情况(传递给被memo的子组件)
const handleClick2 = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Simple: {simpleValue}</p>
<p>Expensive: {expensiveValue}</p>
<button onClick={handleClick1}>Normal</button>
<MemoizedChild onClick={handleClick2} />
</div>
);
}
function heavyComputation(n) {
// 模拟昂贵计算
return n;
}
const MemoizedChild = React.memo(({ onClick }) => {
return <button onClick={onClick}>Memoized</button>;
});
export { UseMemoExample, UseCallbackExample, SearchAndSort, WhenToUse };
47. 自定义Hooks如何创建和使用?
答案: 自定义Hooks是以"use"开头的函数,用于复用状态逻辑。
示例代码:
import { useState, useEffect, useCallback, useRef } from 'react';
// 1. useLocalStorage - 本地存储同步
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用示例
function LocalStorageExample() {
const [name, setName] = useLocalStorage('name', 'Guest');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
);
}
// 2. useFetch - 数据获取
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const json = await response.json();
if (isMounted) {
setData(json);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchData();
return () => {
isMounted = false;
};
}, [url]);
return { data, loading, error };
}
// 使用示例
function FetchExample() {
const { data, loading, error } = useFetch('https://api.example.com/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data?.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
// 3. useDebounce - 防抖
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
// 使用示例
function SearchWithDebounce() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearch) {
console.log('Searching for:', debouncedSearch);
// 执行搜索API调用
}
}, [debouncedSearch]);
return (
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}
// 4. useWindowSize - 窗口尺寸
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
// 使用示例
function ResponsiveComponent() {
const { width } = useWindowSize();
return (
<div>
<p>Window width: {width}px</p>
{width < 768 ? <p>Mobile view</p> : <p>Desktop view</p>}
</div>
);
}
// 5. useToggle - 切换布尔值
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(v => !v);
}, []);
return [value, toggle, setValue];
}
// 使用示例
function ToggleExample() {
const [isOn, toggle] = useToggle(false);
return (
<div>
<p>Status: {isOn ? 'ON' : 'OFF'}</p>
<button onClick={toggle}>Toggle</button>
</div>
);
}
// 6. useInterval - 定时器
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const id = setInterval(() => {
savedCallback.current();
}, delay);
return () => clearInterval(id);
}
}, [delay]);
}
// 使用示例
function Clock() {
const [time, setTime] = useState(new Date());
useInterval(() => {
setTime(new Date());
}, 1000);
return <p>{time.toLocaleTimeString()}</p>;
}
// 7. usePrevious - 前一个值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
// 8. useOnClickOutside - 点击外部
function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]);
}
// 使用示例
function DropdownMenu() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef();
useOnClickOutside(ref, () => setIsOpen(false));
return (
<div ref={ref}>
<button onClick={() => setIsOpen(!isOpen)}>Menu</button>
{isOpen && (
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
)}
</div>
);
}
export {
useLocalStorage,
useFetch,
useDebounce,
useWindowSize,
useToggle,
useInterval,
usePrevious,
useOnClickOutside
};
48-50. 更多React Hooks问题
由于篇幅限制,这里简要列出剩余题目要点:
48. useLayoutEffect vs useEffect?
- useLayoutEffect: 同步执行,DOM更新后、浏览器绘制前
- useEffect: 异步执行,浏览器绘制后
- 用于需要立即读取/修改DOM布局的场景
49. useImperativeHandle的使用?
- 自定义ref暴露给父组件的实例值
- 配合forwardRef使用
- 隐藏组件内部实现细节
50. React 18新增Hooks?
- useId: 生成唯一ID
- useTransition: 标记低优先级更新
- useDeferredValue: 延迟更新值
- useSyncExternalStore: 订阅外部store
- useInsertionEffect: CSS-in-JS库使用
三、React高级主题(10道)
51. React性能优化技巧?
答案:
- 使用React.memo避免不必要的重新渲染
- useMemo和useCallback缓存值和函数
- 代码分割(React.lazy和Suspense)
- 虚拟化长列表(react-window)
- 避免内联对象和函数
- 使用生产环境构建
- 使用key优化列表渲染
示例代码:
import React, { lazy, Suspense, memo, useMemo } from 'react';
// 1. React.memo
const ExpensiveComponent = memo(({ data }) => {
console.log('Rendering expensive component');
return <div>{data}</div>;
});
// 2. 代码分割
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
// 3. 虚拟化长列表
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>Item {items[index]}</div>
);
return (
<FixedSizeList
height={400}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}
// 4. 避免内联对象
// ❌ Bad
function Bad() {
return <MyComponent style={{ margin: 10 }} />;
}
// ✅ Good
const style = { margin: 10 };
function Good() {
return <MyComponent style={style} />;
}
52. React Router的使用?
答案: React Router是React的标准路由库,用于构建单页应用(SPA)。
核心概念:
- BrowserRouter: 使用HTML5 history API
- Routes/Route: 定义路由规则
- Link/NavLink: 导航链接
- useNavigate: 编程式导航
- useParams: 获取URL参数
- useLocation: 获取当前位置
示例代码:
import {
BrowserRouter,
Routes,
Route,
Link,
NavLink,
useNavigate,
useParams,
useLocation,
Navigate,
Outlet
} from 'react-router-dom';
import { useState } from 'react';
// 1. 基本路由设置
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/users">Users</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
// 2. 动态路由
function UsersApp() {
return (
<Routes>
<Route path="/users" element={<UserList />} />
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/users/:userId/posts/:postId" element={<PostDetail />} />
</Routes>
);
}
function UserProfile() {
const { userId } = useParams();
const navigate = useNavigate();
return (
<div>
<h2>User Profile: {userId}</h2>
<button onClick={() => navigate('/users')}>Back to List</button>
<button onClick={() => navigate(-1)}>Go Back</button>
</div>
);
}
// 3. 嵌套路由
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<nav>
<Link to="overview">Overview</Link>
<Link to="stats">Statistics</Link>
<Link to="settings">Settings</Link>
</nav>
<Routes>
<Route path="/" element={<DashboardLayout />}>
<Route index element={<Overview />} />
<Route path="overview" element={<Overview />} />
<Route path="stats" element={<Statistics />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</div>
);
}
function DashboardLayout() {
return (
<div>
<aside>Sidebar</aside>
<main>
<Outlet /> {/* 子路由渲染位置 */}
</main>
</div>
);
}
// 4. 受保护的路由
function ProtectedRoute({ children }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
function useAuth() {
const [user] = useState(null); // 实际应从context获取
return { user };
}
function AppWithAuth() {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/public" element={<PublicPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
);
}
// 5. NavLink - 活动链接样式
function Navigation() {
return (
<nav>
<NavLink
to="/"
className={({ isActive }) => isActive ? 'active' : ''}
style={({ isActive }) => ({
color: isActive ? 'red' : 'black'
})}
>
Home
</NavLink>
</nav>
);
}
// 6. 编程式导航
function LoginForm() {
const navigate = useNavigate();
const location = useLocation();
const handleLogin = async (credentials) => {
// 登录逻辑
const from = location.state?.from?.pathname || '/';
navigate(from, { replace: true });
};
return <form onSubmit={handleLogin}>...</form>;
}
// 7. 查询参数
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q') || '';
const page = searchParams.get('page') || '1';
const handleSearch = (newQuery) => {
setSearchParams({ q: newQuery, page: '1' });
};
return (
<div>
<input value={query} onChange={(e) => handleSearch(e.target.value)} />
<p>Page: {page}</p>
</div>
);
}
// 8. 懒加载路由
import { lazy, Suspense } from 'react';
const LazyAbout = lazy(() => import('./pages/About'));
const LazyDashboard = lazy(() => import('./pages/Dashboard'));
function AppWithLazy() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<LazyAbout />} />
<Route path="/dashboard" element={<LazyDashboard />} />
</Routes>
</Suspense>
);
}
// 辅助组件
const Home = () => <h1>Home Page</h1>;
const About = () => <h1>About Page</h1>;
const Users = () => <h1>Users Page</h1>;
const UserList = () => <h1>User List</h1>;
const PostDetail = () => {
const { userId, postId } = useParams();
return <h1>Post {postId} by User {userId}</h1>;
};
const NotFound = () => <h1>404 - Page Not Found</h1>;
const Overview = () => <div>Overview Content</div>;
const Statistics = () => <div>Statistics Content</div>;
const Settings = () => <div>Settings Content</div>;
const Login = () => <div>Login Page</div>;
const PublicPage = () => <div>Public Page</div>;
53. 状态管理(Redux/Zustand)?
答案: 状态管理库用于管理跨组件的全局状态。
Redux Toolkit - 官方推荐的Redux工具集 Zustand - 轻量级替代方案
示例代码:
// ==================== Redux Toolkit ====================
import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { Provider, useSelector, useDispatch } from 'react-redux';
// 1. 创建Slice
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
status: 'idle'
},
reducers: {
increment: (state) => {
state.value += 1; // Immer允许直接修改
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
reset: (state) => {
state.value = 0;
}
},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUserById.fulfilled, (state, action) => {
state.status = 'succeeded';
state.user = action.payload;
})
.addCase(fetchUserById.rejected, (state) => {
state.status = 'failed';
});
}
});
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
// 2. 异步操作
const fetchUserById = createAsyncThunk(
'users/fetchById',
async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
);
// 3. 创建Store
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
// 其他reducers...
}
});
// 4. 在组件中使用
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
<button onClick={() => dispatch(reset())}>Reset</button>
</div>
);
}
// 5. 复杂的Slice示例 - Todo应用
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
filter: 'all'
},
reducers: {
addTodo: (state, action) => {
state.items.push({
id: Date.now(),
text: action.payload,
completed: false
});
},
toggleTodo: (state, action) => {
const todo = state.items.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodo: (state, action) => {
state.items = state.items.filter(t => t.id !== action.payload);
},
setFilter: (state, action) => {
state.filter = action.payload;
}
}
});
// 6. Selectors
const selectAllTodos = (state) => state.todos.items;
const selectFilter = (state) => state.todos.filter;
const selectFilteredTodos = (state) => {
const todos = selectAllTodos(state);
const filter = selectFilter(state);
if (filter === 'active') return todos.filter(t => !t.completed);
if (filter === 'completed') return todos.filter(t => t.completed);
return todos;
};
// 7. App组件
function AppWithRedux() {
return (
<Provider store={store}>
<Counter />
<TodoList />
</Provider>
);
}
// ==================== Zustand ====================
import create from 'zustand';
import { persist } from 'zustand/middleware';
// 1. 基本Store
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 })
}));
// 2. 使用Store
function ZustandCounter() {
const { count, increment, decrement, reset } = useCounterStore();
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
// 3. 带异步操作的Store
const useUserStore = create((set, get) => ({
user: null,
loading: false,
error: null,
fetchUser: async (userId) => {
set({ loading: true, error: null });
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
set({ user, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
updateUser: (updates) => {
set((state) => ({
user: { ...state.user, ...updates }
}));
},
logout: () => {
set({ user: null });
}
}));
// 4. 持久化Store
const useTodoStore = create(
persist(
(set) => ({
todos: [],
addTodo: (text) => set((state) => ({
todos: [...state.todos, { id: Date.now(), text, completed: false }]
})),
toggleTodo: (id) => set((state) => ({
todos: state.todos.map(t =>
t.id === id ? { ...t, completed: !t.completed } : t
)
})),
deleteTodo: (id) => set((state) => ({
todos: state.todos.filter(t => t.id !== id)
}))
}),
{ name: 'todo-storage' }
)
);
// 5. 分割选择器(避免不必要的渲染)
function TodoCount() {
const count = useTodoStore((state) => state.todos.length);
return <p>Total todos: {count}</p>;
}
// 6. 中间件
const useStoreWithLog = create((set) => ({
count: 0,
increment: () => set((state) => {
console.log('Before:', state.count);
const newState = { count: state.count + 1 };
console.log('After:', newState.count);
return newState;
})
}));
// 7. 多个Store组合
const useAuthStore = create((set) => ({
token: null,
setToken: (token) => set({ token }),
clearToken: () => set({ token: null })
}));
const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({
items: [...state.items, item]
})),
clearCart: () => set({ items: [] })
}));
function ShoppingApp() {
const { token } = useAuthStore();
const { items, addItem } = useCartStore();
return (
<div>
<p>Auth: {token ? 'Logged in' : 'Guest'}</p>
<p>Cart items: {items.length}</p>
</div>
);
}
54. React Query/SWR数据获取?
答案: 专门用于数据获取、缓存和同步的库。
特点:
- 自动缓存
- 后台重新验证
- 重复数据删除
- 自动重试
- 分页/无限滚动支持
示例代码:
// ==================== React Query ====================
import {
QueryClient,
QueryClientProvider,
useQuery,
useMutation,
useQueryClient,
useInfiniteQuery
} from '@tanstack/react-query';
// 1. 设置Query Client
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟
cacheTime: 10 * 60 * 1000, // 10分钟
retry: 3,
refetchOnWindowFocus: true
}
}
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserList />
</QueryClientProvider>
);
}
// 2. 基本查询
function UserList() {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to fetch');
return response.json();
}
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<button onClick={refetch}>Refresh</button>
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
// 3. 带参数的查询
function UserProfile({ userId }) {
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId // 条件查询
});
return <div>{user?.name}</div>;
}
async function fetchUser(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
// 4. Mutation(增删改)
function CreateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (newUser) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser)
});
return response.json();
},
onSuccess: () => {
// 使缓存失效,重新获取
queryClient.invalidateQueries({ queryKey: ['users'] });
},
onError: (error) => {
console.error('Error creating user:', error);
}
});
const handleSubmit = (e) => {
e.preventDefault();
mutation.mutate({ name: 'John Doe', email: 'john@example.com' });
};
return (
<form onSubmit={handleSubmit}>
<button type="submit" disabled={mutation.isLoading}>
{mutation.isLoading ? 'Creating...' : 'Create User'}
</button>
{mutation.isError && <p>Error: {mutation.error.message}</p>}
{mutation.isSuccess && <p>User created!</p>}
</form>
);
}
// 5. 乐观更新
function UpdateUser({ userId }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: updateUser,
onMutate: async (newData) => {
// 取消正在进行的查询
await queryClient.cancelQueries({ queryKey: ['user', userId] });
// 保存当前数据(用于回滚)
const previousUser = queryClient.getQueryData(['user', userId]);
// 乐观更新
queryClient.setQueryData(['user', userId], (old) => ({
...old,
...newData
}));
return { previousUser };
},
onError: (err, newData, context) => {
// 回滚
queryClient.setQueryData(['user', userId], context.previousUser);
},
onSettled: () => {
// 重新获取确保同步
queryClient.invalidateQueries({ queryKey: ['user', userId] });
}
});
return (
<button onClick={() => mutation.mutate({ name: 'Updated Name' })}>
Update
</button>
);
}
async function updateUser(data) {
const response = await fetch(`/api/users/${data.id}`, {
method: 'PUT',
body: JSON.stringify(data)
});
return response.json();
}
// 6. 无限滚动
function InfiniteUserList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['users', 'infinite'],
queryFn: ({ pageParam = 1 }) => fetchUsers(pageParam),
getNextPageParam: (lastPage, pages) => {
return lastPage.hasMore ? pages.length + 1 : undefined;
}
});
return (
<div>
{data?.pages.map((page, i) => (
<div key={i}>
{page.users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
))}
{hasNextPage && (
<button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}
async function fetchUsers(page) {
const response = await fetch(`/api/users?page=${page}`);
return response.json();
}
// 7. 依赖查询
function UserPosts({ userId }) {
// 先获取用户
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId)
});
// 基于用户数据获取帖子
const { data: posts } = useQuery({
queryKey: ['posts', user?.id],
queryFn: () => fetchPosts(user.id),
enabled: !!user // 只在user存在时执行
});
return <div>...</div>;
}
// ==================== SWR ====================
import useSWR, { mutate } from 'swr';
// 1. 基本用法
const fetcher = (url) => fetch(url).then(r => r.json());
function SWRUserList() {
const { data, error, isLoading } = useSWR('/api/users', fetcher);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
// 2. 自动重新验证
function SWRProfile({ userId }) {
const { data } = useSWR(
`/api/users/${userId}`,
fetcher,
{
refreshInterval: 3000, // 每3秒刷新
revalidateOnFocus: true,
revalidateOnReconnect: true
}
);
return <div>{data?.name}</div>;
}
// 3. 手动触发重新验证
function SWRButton() {
const handleRefresh = () => {
mutate('/api/users'); // 重新验证特定key
};
return <button onClick={handleRefresh}>Refresh</button>;
}
// 4. 条件获取
function ConditionalFetch({ shouldFetch, userId }) {
const { data } = useSWR(
shouldFetch ? `/api/users/${userId}` : null,
fetcher
);
return <div>{data?.name}</div>;
}
// 5. 分页
function SWRPagination() {
const [page, setPage] = useState(1);
const { data, error } = useSWR(`/api/users?page=${page}`, fetcher);
return (
<div>
{data?.users.map(user => <div key={user.id}>{user.name}</div>)}
<button onClick={() => setPage(page + 1)}>Next Page</button>
</div>
);
}
// 6. 全局配置
import { SWRConfig } from 'swr';
function AppWithSWR() {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: false,
shouldRetryOnError: true
}}
>
<SWRUserList />
</SWRConfig>
);
}
55. 表单处理(React Hook Form/Formik)?
答案: 表单库简化表单验证、错误处理和提交。
React Hook Form - 性能更好,基于非受控组件 Formik - 功能丰富,基于受控组件
示例代码:
// ==================== React Hook Form ====================
import { useForm, Controller } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
// 1. 基本表单
function BasicForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting }
} = useForm();
const onSubmit = async (data) => {
console.log(data);
await new Promise(resolve => setTimeout(resolve, 1000));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('username', {
required: 'Username is required',
minLength: { value: 3, message: 'Min length is 3' }
})}
placeholder="Username"
/>
{errors.username && <p>{errors.username.message}</p>}
<input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email'
}
})}
placeholder="Email"
/>
{errors.email && <p>{errors.email.message}</p>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}
// 2. 使用Yup验证
const schema = yup.object({
username: yup.string().min(3).max(20).required(),
email: yup.string().email().required(),
password: yup.string().min(8).required(),
confirmPassword: yup.string()
.oneOf([yup.ref('password')], 'Passwords must match')
.required(),
age: yup.number().positive().integer().min(18).required(),
terms: yup.boolean().oneOf([true], 'Must accept terms')
}).required();
function FormWithValidation() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm({
resolver: yupResolver(schema),
defaultValues: {
username: '',
email: '',
password: '',
confirmPassword: '',
age: 18,
terms: false
}
});
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register('username')} />
<p>{errors.username?.message}</p>
<input {...register('email')} />
<p>{errors.email?.message}</p>
<input type="password" {...register('password')} />
<p>{errors.password?.message}</p>
<input type="password" {...register('confirmPassword')} />
<p>{errors.confirmPassword?.message}</p>
<input type="number" {...register('age')} />
<p>{errors.age?.message}</p>
<label>
<input type="checkbox" {...register('terms')} />
Accept Terms
</label>
<p>{errors.terms?.message}</p>
<button type="submit">Submit</button>
</form>
);
}
// 3. 动态字段
function DynamicForm() {
const { register, handleSubmit, watch } = useForm();
const watchShowAge = watch('showAge', false);
return (
<form onSubmit={handleSubmit(console.log)}>
<label>
<input type="checkbox" {...register('showAge')} />
Show Age Field
</label>
{watchShowAge && (
<input
type="number"
{...register('age', { valueAsNumber: true })}
placeholder="Age"
/>
)}
<button type="submit">Submit</button>
</form>
);
}
// 4. 字段数组
import { useFieldArray } from 'react-hook-form';
function FieldArrayForm() {
const { register, control, handleSubmit } = useForm({
defaultValues: {
items: [{ name: '', quantity: 1 }]
}
});
const { fields, append, remove } = useFieldArray({
control,
name: 'items'
});
return (
<form onSubmit={handleSubmit(console.log)}>
{fields.map((field, index) => (
<div key={field.id}>
<input
{...register(`items.${index}.name`)}
placeholder="Item name"
/>
<input
type="number"
{...register(`items.${index}.quantity`, { valueAsNumber: true })}
placeholder="Quantity"
/>
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button type="button" onClick={() => append({ name: '', quantity: 1 })}>
Add Item
</button>
<button type="submit">Submit</button>
</form>
);
}
// 5. 受控组件集成(如UI库)
function ControlledComponentForm() {
const { control, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(console.log)}>
<Controller
name="select"
control={control}
defaultValue=""
render={({ field }) => (
<select {...field}>
<option value="">Select...</option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
)}
/>
<button type="submit">Submit</button>
</form>
);
}
// ==================== Formik ====================
import { Formik, Form, Field, ErrorMessage, FieldArray } from 'formik';
// 1. 基本Formik表单
function FormikBasic() {
const initialValues = {
username: '',
email: '',
password: ''
};
const validate = (values) => {
const errors = {};
if (!values.username) {
errors.username = 'Required';
} else if (values.username.length < 3) {
errors.username = 'Must be at least 3 characters';
}
if (!values.email) {
errors.email = 'Required';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) {
errors.email = 'Invalid email';
}
return errors;
};
const handleSubmit = (values, { setSubmitting }) => {
console.log(values);
setTimeout(() => {
setSubmitting(false);
}, 1000);
};
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={handleSubmit}
>
{({ isSubmitting }) => (
<Form>
<Field name="username" placeholder="Username" />
<ErrorMessage name="username" component="div" />
<Field name="email" type="email" placeholder="Email" />
<ErrorMessage name="email" component="div" />
<Field name="password" type="password" placeholder="Password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</Form>
)}
</Formik>
);
}
// 2. 使用Yup验证
const formikSchema = yup.object({
username: yup.string().min(3).required(),
email: yup.string().email().required(),
password: yup.string().min(8).required()
});
function FormikWithYup() {
return (
<Formik
initialValues={{ username: '', email: '', password: '' }}
validationSchema={formikSchema}
onSubmit={(values) => console.log(values)}
>
<Form>
<Field name="username" />
<ErrorMessage name="username" />
<Field name="email" />
<ErrorMessage name="email" />
<Field name="password" type="password" />
<ErrorMessage name="password" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
}
// 3. 字段数组
function FormikFieldArray() {
return (
<Formik
initialValues={{ friends: [''] }}
onSubmit={(values) => console.log(values)}
>
{({ values }) => (
<Form>
<FieldArray name="friends">
{({ push, remove }) => (
<div>
{values.friends.map((friend, index) => (
<div key={index}>
<Field name={`friends.${index}`} placeholder="Friend name" />
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button type="button" onClick={() => push('')}>
Add Friend
</button>
</div>
)}
</FieldArray>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
}
// 4. 自定义Field组件
function CustomInput({ field, form, ...props }) {
return (
<div>
<input {...field} {...props} />
{form.touched[field.name] && form.errors[field.name] && (
<div className="error">{form.errors[field.name]}</div>
)}
</div>
);
}
function FormikCustomField() {
return (
<Formik
initialValues={{ email: '' }}
onSubmit={console.log}
>
<Form>
<Field name="email" component={CustomInput} type="email" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
}
(继续下一部分...)
56. React样式解决方案?
答案: React中有多种样式方案,各有优缺点。
主要方案:
- CSS Modules - 局部作用域CSS
- Styled Components - CSS-in-JS
- Tailwind CSS - 实用优先的CSS框架
- Emotion - 高性能CSS-in-JS
示例代码:
// ==================== 1. CSS Modules ====================
// styles.module.css
/*
.container {
padding: 20px;
background: #f0f0f0;
}
.button {
padding: 10px 20px;
background: blue;
color: white;
border: none;
border-radius: 4px;
}
.button:hover {
background: darkblue;
}
*/
// Component.jsx
import styles from './styles.module.css';
function CSSModulesExample() {
return (
<div className={styles.container}>
<button className={styles.button}>Click Me</button>
<button className={`${styles.button} ${styles.primary}`}>
Primary Button
</button>
</div>
);
}
// 条件类名
import classNames from 'classnames';
function ConditionalStyles({ isPrimary, isDisabled }) {
return (
<button
className={classNames(styles.button, {
[styles.primary]: isPrimary,
[styles.disabled]: isDisabled
})}
>
Button
</button>
);
}
// ==================== 2. Styled Components ====================
import styled from 'styled-components';
// 基本样式
const Button = styled.button`
padding: 10px 20px;
background: ${props => props.primary ? 'blue' : 'gray'};
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background: ${props => props.primary ? 'darkblue' : 'darkgray'};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
const Container = styled.div`
padding: 20px;
max-width: 1200px;
margin: 0 auto;
`;
function StyledComponentsExample() {
return (
<Container>
<Button>Normal Button</Button>
<Button primary>Primary Button</Button>
<Button disabled>Disabled</Button>
</Container>
);
}
// 扩展样式
const PrimaryButton = styled(Button)`
background: green;
font-weight: bold;
&:hover {
background: darkgreen;
}
`;
// 动态样式
const DynamicButton = styled.button`
padding: ${props => props.size === 'large' ? '15px 30px' : '10px 20px'};
font-size: ${props => props.size === 'large' ? '18px' : '14px'};
background: ${props => props.variant === 'danger' ? 'red' : 'blue'};
`;
function DynamicExample() {
return (
<>
<DynamicButton size="large" variant="danger">
Large Danger
</DynamicButton>
<DynamicButton>Normal</DynamicButton>
</>
);
}
// 主题支持
import { ThemeProvider } from 'styled-components';
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
success: '#28a745',
danger: '#dc3545'
},
spacing: {
small: '8px',
medium: '16px',
large: '24px'
}
};
const ThemedButton = styled.button`
background: ${props => props.theme.colors.primary};
padding: ${props => props.theme.spacing.medium};
color: white;
border: none;
border-radius: 4px;
`;
function ThemedApp() {
return (
<ThemeProvider theme={theme}>
<ThemedButton>Themed Button</ThemedButton>
</ThemeProvider>
);
}
// 全局样式
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: ${props => props.theme.colors.background};
}
`;
function AppWithGlobalStyles() {
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<div>App Content</div>
</ThemeProvider>
);
}
// ==================== 3. Tailwind CSS ====================
// 需要配置 tailwind.config.js 和安装依赖
function TailwindExample() {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-blue-600 mb-4">
Tailwind Heading
</h1>
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Button
</button>
<button className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded ml-2">
Danger Button
</button>
{/* 响应式设计 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="bg-white p-4 rounded shadow">Card 1</div>
<div className="bg-white p-4 rounded shadow">Card 2</div>
<div className="bg-white p-4 rounded shadow">Card 3</div>
</div>
{/* 暗黑模式 */}
<div className="bg-white dark:bg-gray-800 text-black dark:text-white p-4">
Dark mode support
</div>
</div>
);
}
// 自定义类
function TailwindCustom() {
return (
<button className="btn-primary">
Custom Button
</button>
);
}
// tailwind.config.js 配置
/*
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
brand: '#007bff'
}
}
},
plugins: []
}
*/
// 条件类名
import clsx from 'clsx';
function ConditionalTailwind({ isPrimary, isLarge }) {
return (
<button
className={clsx(
'px-4 py-2 rounded font-semibold',
isPrimary ? 'bg-blue-500 text-white' : 'bg-gray-200 text-black',
isLarge ? 'text-lg' : 'text-sm'
)}
>
Button
</button>
);
}
// ==================== 4. Emotion ====================
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import styled from '@emotion/styled';
// CSS prop
function EmotionCSS() {
return (
<div
css={css`
padding: 20px;
background: #f0f0f0;
border-radius: 8px;
&:hover {
background: #e0e0e0;
}
`}
>
Emotion CSS Prop
</div>
);
}
// Styled components
const EmotionButton = styled.button`
padding: 10px 20px;
background: ${props => props.primary ? 'blue' : 'gray'};
color: white;
border: none;
border-radius: 4px;
`;
function EmotionStyled() {
return (
<>
<EmotionButton>Normal</EmotionButton>
<EmotionButton primary>Primary</EmotionButton>
</>
);
}
// 动态样式
const dynamicStyle = (color) => css`
color: ${color};
font-weight: bold;
`;
function EmotionDynamic() {
return (
<div css={dynamicStyle('red')}>
Dynamic Color
</div>
);
}
// ==================== 5. 内联样式 ====================
function InlineStyles() {
const containerStyle = {
padding: '20px',
background: '#f0f0f0',
borderRadius: '8px'
};
const buttonStyle = {
padding: '10px 20px',
background: 'blue',
color: 'white',
border: 'none',
borderRadius: '4px'
};
return (
<div style={containerStyle}>
<button style={buttonStyle}>Button</button>
</div>
);
}
// ==================== 样式方案对比 ====================
/*
CSS Modules:
优点:作用域隔离、传统CSS、SSR友好
缺点:需要导入、动态样式不方便
Styled Components:
优点:动态样式、主题支持、自动vendor前缀
缺点:运行时开销、学习曲线
Tailwind CSS:
优点:快速开发、一致性、小体积(tree-shaking)
缺点:类名冗长、学习成本
Emotion:
优点:性能好、灵活、SSR支持
缺点:配置复杂
*/
57. React测试(Jest + React Testing Library)?
答案: 测试确保代码质量和可维护性。
测试类型:
- 单元测试:测试单个组件/函数
- 集成测试:测试组件交互
- E2E测试:测试完整用户流程
示例代码:
// ==================== Jest 基础 ====================
// sum.js
export function sum(a, b) {
return a + b;
}
// sum.test.js
import { sum } from './sum';
describe('sum function', () => {
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('adds negative numbers', () => {
expect(sum(-1, -2)).toBe(-3);
});
it('handles zero', () => {
expect(sum(0, 5)).toBe(5);
});
});
// ==================== React Testing Library ====================
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
// 1. 基本组件测试
// Button.jsx
function Button({ onClick, children, disabled }) {
return (
<button onClick={onClick} disabled={disabled}>
{children}
</button>
);
}
// Button.test.jsx
describe('Button Component', () => {
test('renders button with text', () => {
render(<Button>Click Me</Button>);
const button = screen.getByText('Click Me');
expect(button).toBeInTheDocument();
});
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click Me</Button>);
const button = screen.getByText('Click Me');
fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('is disabled when disabled prop is true', () => {
render(<Button disabled>Click Me</Button>);
const button = screen.getByText('Click Me');
expect(button).toBeDisabled();
});
});
// 2. 测试表单
// LoginForm.jsx
function LoginForm({ onSubmit }) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!username || !password) {
setError('Please fill in all fields');
return;
}
onSubmit({ username, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
aria-label="username"
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
aria-label="password"
/>
{error && <div role="alert">{error}</div>}
<button type="submit">Login</button>
</form>
);
}
// LoginForm.test.jsx
describe('LoginForm', () => {
test('submits form with valid data', async () => {
const handleSubmit = jest.fn();
render(<LoginForm onSubmit={handleSubmit} />);
const user = userEvent.setup();
await user.type(screen.getByLabelText('username'), 'john');
await user.type(screen.getByLabelText('password'), 'password123');
await user.click(screen.getByText('Login'));
expect(handleSubmit).toHaveBeenCalledWith({
username: 'john',
password: 'password123'
});
});
test('shows error when fields are empty', async () => {
render(<LoginForm onSubmit={jest.fn()} />);
const user = userEvent.setup();
await user.click(screen.getByText('Login'));
expect(screen.getByRole('alert')).toHaveTextContent('Please fill in all fields');
});
});
// 3. 异步测试
// UserProfile.jsx
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// UserProfile.test.jsx
describe('UserProfile', () => {
test('displays user data after loading', async () => {
// Mock fetch
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({
name: 'John Doe',
email: 'john@example.com'
})
})
);
render(<UserProfile userId={1} />);
// 初始loading状态
expect(screen.getByText('Loading...')).toBeInTheDocument();
// 等待数据加载
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
test('displays error message on fetch failure', async () => {
global.fetch = jest.fn(() =>
Promise.reject(new Error('Failed to fetch'))
);
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText(/Error: Failed to fetch/)).toBeInTheDocument();
});
});
});
// 4. 测试Context
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}
// ThemedButton.test.jsx
describe('ThemedButton', () => {
test('toggles theme', async () => {
render(
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
const button = screen.getByRole('button');
expect(button).toHaveTextContent('Current theme: light');
await userEvent.click(button);
expect(button).toHaveTextContent('Current theme: dark');
await userEvent.click(button);
expect(button).toHaveTextContent('Current theme: light');
});
});
// 5. 测试自定义Hook
import { renderHook, act } from '@testing-library/react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(c => c + 1);
const decrement = () => setCount(c => c - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
describe('useCounter', () => {
test('initializes with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
test('initializes with custom value', () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
test('increments count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
test('resets count', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.increment();
result.current.increment();
});
expect(result.current.count).toBe(7);
act(() => {
result.current.reset();
});
expect(result.current.count).toBe(5);
});
});
// 6. Mock模块
// api.js
export async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// component.test.js
import * as api from './api';
jest.mock('./api');
test('fetches user data', async () => {
api.fetchUser.mockResolvedValue({
id: 1,
name: 'John'
});
// 测试使用api.fetchUser的组件
});
// 7. 测试覆盖率
// package.json
/*
{
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!src/index.js"
],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
*/
// 8. 快照测试
test('matches snapshot', () => {
const { container } = render(<Button>Click Me</Button>);
expect(container.firstChild).toMatchSnapshot();
});
58. TypeScript与React?
答案: TypeScript为React提供类型安全,提高代码质量和开发体验。
核心概念:
- Props类型定义
- State类型定义
- 事件类型
- Ref类型
- 泛型组件
示例代码:
// ==================== 1. 基本Props类型 ====================
interface ButtonProps {
children: React.ReactNode;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
function Button({ children, onClick, variant = 'primary', disabled }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled} className={variant}>
{children}
</button>
);
}
// 使用type
type CardProps = {
title: string;
description?: string;
footer?: React.ReactNode;
};
const Card: React.FC<CardProps> = ({ title, description, footer }) => {
return (
<div>
<h2>{title}</h2>
{description && <p>{description}</p>}
{footer}
</div>
);
};
// ==================== 2. State类型 ====================
interface User {
id: number;
name: string;
email: string;
}
function UserProfile() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');
// 数组状态
const [users, setUsers] = useState<User[]>([]);
// 对象状态
const [formData, setFormData] = useState<{
username: string;
email: string;
}>({
username: '',
email: ''
});
return <div>{user?.name}</div>;
}
// ==================== 3. 事件类型 ====================
function EventHandlers() {
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
console.log('Enter pressed');
}
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} onKeyDown={handleKeyDown} />
<button onClick={handleClick}>Submit</button>
</form>
);
}
// ==================== 4. Ref类型 ====================
function RefExample() {
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);
const timerRef = useRef<number | null>(null);
const focusInput = () => {
inputRef.current?.focus();
};
const getHeight = () => {
const height = divRef.current?.offsetHeight;
console.log(height);
};
useEffect(() => {
timerRef.current = window.setTimeout(() => {
console.log('Timer expired');
}, 1000);
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, []);
return (
<>
<input ref={inputRef} />
<div ref={divRef}>Content</div>
<button onClick={focusInput}>Focus</button>
</>
);
}
// ==================== 5. 泛型组件 ====================
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string | number;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}
// 使用泛型组件
interface User {
id: number;
name: string;
}
function UserList() {
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
return (
<List
items={users}
keyExtractor={(user) => user.id}
renderItem={(user) => <span>{user.name}</span>}
/>
);
}
// ==================== 6. Context类型 ====================
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// ==================== 7. 自定义Hook类型 ====================
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: string | null;
refetch: () => void;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}
// 使用
interface Post {
id: number;
title: string;
body: string;
}
function Posts() {
const { data, loading, error } = useFetch<Post[]>('/api/posts');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{data?.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
// ==================== 8. 高阶组件类型 ====================
interface WithLoadingProps {
loading: boolean;
}
function withLoading<P extends object>(
Component: React.ComponentType<P>
): React.FC<P & WithLoadingProps> {
return ({ loading, ...props }) => {
if (loading) {
return <div>Loading...</div>;
}
return <Component {...(props as P)} />;
};
}
// 使用
interface UserCardProps {
name: string;
email: string;
}
const UserCard: React.FC<UserCardProps> = ({ name, email }) => (
<div>
<h3>{name}</h3>
<p>{email}</p>
</div>
);
const UserCardWithLoading = withLoading(UserCard);
// ==================== 9. 组件Props类型工具 ====================
// 提取组件Props类型
type ButtonElementProps = React.ComponentProps<'button'>;
type DivElementProps = React.ComponentPropsWithoutRef<'div'>;
// Omit某些props
interface ExtendedButtonProps extends Omit<ButtonElementProps, 'type'> {
variant: 'primary' | 'secondary';
}
// Pick某些props
type OnlyOnClick = Pick<ButtonElementProps, 'onClick'>;
// ==================== 10. 实用类型 ====================
// React.ReactNode - 任何可渲染内容
type Children = React.ReactNode;
// React.ReactElement - React元素
type Element = React.ReactElement;
// React.CSSProperties - CSS样式对象
const style: React.CSSProperties = {
color: 'red',
fontSize: '16px'
};
// React.HTMLAttributes - HTML属性
interface DivProps extends React.HTMLAttributes<HTMLDivElement> {
customProp: string;
}
(继续最后两题...)
59. Next.js服务端渲染?
答案: Next.js是React框架,支持SSR、SSG、ISR等多种渲染模式。
渲染模式:
- SSR (Server-Side Rendering): 每次请求时服务器渲染
- SSG (Static Site Generation): 构建时预渲染
- ISR (Incremental Static Regeneration): 按需重新生成静态页面
- CSR (Client-Side Rendering): 客户端渲染
示例代码:
// ==================== 1. SSR (getServerSideProps) ====================
// pages/users/[id].js
interface User {
id: number;
name: string;
email: string;
}
interface UserPageProps {
user: User;
timestamp: string;
}
// 每次请求时在服务器端执行
export async function getServerSideProps(context) {
const { params, req, res, query } = context;
const { id } = params;
// 获取数据
const response = await fetch(`https://api.example.com/users/${id}`);
const user = await response.json();
// 设置缓存头
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
);
// 如果用户不存在,返回404
if (!user) {
return {
notFound: true
};
}
// 重定向
if (user.banned) {
return {
redirect: {
destination: '/banned',
permanent: false
}
};
}
return {
props: {
user,
timestamp: new Date().toISOString()
}
};
}
function UserPage({ user, timestamp }: UserPageProps) {
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
<p>Rendered at: {timestamp}</p>
</div>
);
}
export default UserPage;
// ==================== 2. SSG (getStaticProps) ====================
// pages/blog/[slug].js
interface Post {
slug: string;
title: string;
content: string;
publishedAt: string;
}
interface BlogPostProps {
post: Post;
}
// 构建时生成所有可能的路径
export async function getStaticPaths() {
const response = await fetch('https://api.example.com/posts');
const posts = await response.json();
const paths = posts.map((post) => ({
params: { slug: post.slug }
}));
return {
paths,
fallback: 'blocking' // 或 false, true
// false: 返回404如果路径不在paths中
// true: 显示fallback页面,后台生成页面
// 'blocking': 等待页面生成完成
};
}
// 构建时为每个路径生成HTML
export async function getStaticProps({ params }) {
const { slug } = params;
const response = await fetch(`https://api.example.com/posts/${slug}`);
const post = await response.json();
if (!post) {
return {
notFound: true
};
}
return {
props: {
post
},
revalidate: 60 // ISR: 60秒后重新生成
};
}
function BlogPost({ post }: BlogPostProps) {
const router = useRouter();
// fallback: true时显示加载状态
if (router.isFallback) {
return <div>Loading...</div>;
}
return (
<article>
<h1>{post.title}</h1>
<time>{post.publishedAt}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
export default BlogPost;
// ==================== 3. ISR (Incremental Static Regeneration) ====================
// pages/products/[id].js
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id);
return {
props: {
product
},
revalidate: 10 // 10秒后重新生成
};
}
export async function getStaticPaths() {
// 只预生成热门产品
const popularProducts = await fetchPopularProducts();
return {
paths: popularProducts.map(p => ({ params: { id: p.id } })),
fallback: 'blocking' // 其他产品按需生成
};
}
// ==================== 4. 混合渲染 ====================
// pages/dashboard.js
// 部分SSR + 客户端获取
export async function getServerSideProps() {
// 获取初始数据
const initialData = await fetchInitialData();
return {
props: {
initialData
}
};
}
function Dashboard({ initialData }) {
// 客户端获取用户特定数据
const { data: userData } = useSWR('/api/user', fetcher);
const { data: notifications } = useSWR('/api/notifications', fetcher);
return (
<div>
<h1>Dashboard</h1>
<InitialSection data={initialData} />
{userData && <UserSection user={userData} />}
{notifications && <NotificationsList items={notifications} />}
</div>
);
}
// ==================== 5. API Routes ====================
// pages/api/users/[id].ts
import type { NextApiRequest, NextApiResponse } from 'next';
interface User {
id: number;
name: string;
}
type ResponseData = {
user?: User;
error?: string;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseData>
) {
const { method, query } = req;
const { id } = query;
switch (method) {
case 'GET':
try {
const user = await fetchUser(id as string);
res.status(200).json({ user });
} catch (error) {
res.status(500).json({ error: 'Failed to fetch user' });
}
break;
case 'PUT':
try {
const updatedUser = await updateUser(id as string, req.body);
res.status(200).json({ user: updatedUser });
} catch (error) {
res.status(500).json({ error: 'Failed to update user' });
}
break;
case 'DELETE':
try {
await deleteUser(id as string);
res.status(204).end();
} catch (error) {
res.status(500).json({ error: 'Failed to delete user' });
}
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
// ==================== 6. Middleware ====================
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 认证检查
const token = request.cookies.get('token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
// 添加自定义header
const response = NextResponse.next();
response.headers.set('x-custom-header', 'value');
return response;
}
// 配置哪些路径使用middleware
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
};
// ==================== 7. Image优化 ====================
import Image from 'next/image';
function ImageExample() {
return (
<div>
{/* 自动优化 */}
<Image
src="/profile.jpg"
alt="Profile"
width={500}
height={500}
priority // 优先加载
/>
{/* 外部图片 */}
<Image
src="https://example.com/image.jpg"
alt="External"
width={800}
height={600}
placeholder="blur" // 模糊占位符
blurDataURL="/placeholder.jpg"
/>
{/* 响应式 */}
<Image
src="/banner.jpg"
alt="Banner"
fill
style={{ objectFit: 'cover' }}
/>
</div>
);
}
// next.config.js配置
/*
module.exports = {
images: {
domains: ['example.com', 'cdn.example.com'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384]
}
}
*/
// ==================== 8. 动态导入 ====================
import dynamic from 'next/dynamic';
// 动态导入组件(代码分割)
const DynamicComponent = dynamic(() => import('../components/Heavy'), {
loading: () => <p>Loading...</p>,
ssr: false // 禁用SSR
});
function PageWithDynamic() {
return (
<div>
<h1>Page</h1>
<DynamicComponent />
</div>
);
}
// ==================== 9. Head和SEO ====================
import Head from 'next/head';
function SEOPage() {
return (
<>
<Head>
<title>Page Title</title>
<meta name="description" content="Page description" />
<meta property="og:title" content="Open Graph Title" />
<meta property="og:description" content="OG Description" />
<meta property="og:image" content="https://example.com/image.jpg" />
<link rel="canonical" href="https://example.com/page" />
<script type="application/ld+json">
{JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Article Title'
})}
</script>
</Head>
<article>Content</article>
</>
);
}
// ==================== 10. App Router (Next.js 13+) ====================
// app/layout.tsx
export default function RootLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
// app/page.tsx
export default function HomePage() {
return <h1>Home Page</h1>;
}
// app/blog/[slug]/page.tsx
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 60 } // ISR
});
return res.json();
}
export default async function BlogPost({
params
}: {
params: { slug: string };
}) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// 生成静态参数
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post) => ({
slug: post.slug
}));
}
// 生成元数据
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage]
}
};
}
60. React 18新特性?
答案: React 18引入了并发特性、自动批处理、Transitions等重要更新。
核心新特性:
- Concurrent Rendering (并发渲染)
- Automatic Batching (自动批处理)
- Transitions (过渡)
- Suspense for Data Fetching
- useId, useDeferredValue, useTransition, useSyncExternalStore
示例代码:
// ==================== 1. Concurrent Rendering ====================
// React 18之前
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));
// React 18 - 启用并发特性
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// ==================== 2. Automatic Batching ====================
import { useState } from 'react';
// React 17: 只在事件处理器中批处理
// React 18: 所有更新都自动批处理
function AutomaticBatching() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
// React 17: 在Promise、setTimeout中不批处理(2次渲染)
// React 18: 自动批处理(1次渲染)
function handleClick() {
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 18中只会重新渲染一次
}, 1000);
}
// Promise中也会批处理
async function fetchData() {
const data = await fetch('/api/data');
setCount(c => c + 1);
setFlag(f => !f);
// React 18中只会重新渲染一次
}
console.log('Render'); // React 18中只打印一次
return (
<div>
<p>Count: {count}</p>
<p>Flag: {flag.toString()}</p>
<button onClick={handleClick}>Update</button>
</div>
);
}
// 如果需要退出批处理
import { flushSync } from 'react-dom';
function OptOutBatching() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// 第一次渲染完成
flushSync(() => {
setFlag(f => !f);
});
// 第二次渲染完成
}
return <button onClick={handleClick}>Update</button>;
}
// ==================== 3. useTransition ====================
import { useState, useTransition } from 'react';
function SearchWithTransition() {
const [isPending, startTransition] = useTransition();
const [input, setInput] = useState('');
const [list, setList] = useState([]);
function handleChange(e) {
// 紧急更新:立即更新输入框
setInput(e.target.value);
// 非紧急更新:标记为transition
startTransition(() => {
// 昂贵的计算或状态更新
const filtered = heavyFilter(e.target.value);
setList(filtered);
});
}
return (
<div>
<input value={input} onChange={handleChange} />
{isPending && <div>Searching...</div>}
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
function heavyFilter(query) {
// 模拟昂贵的过滤操作
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`
}));
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}
// 标签切换示例
function TabsWithTransition() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('home');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<div>
<button onClick={() => selectTab('home')}>Home</button>
<button onClick={() => selectTab('posts')}>
Posts {isPending && '(Loading...)'}
</button>
<button onClick={() => selectTab('contact')}>Contact</button>
<div>
{tab === 'home' && <HomePage />}
{tab === 'posts' && <PostsPage />}
{tab === 'contact' && <ContactPage />}
</div>
</div>
);
}
// ==================== 4. useDeferredValue ====================
import { useDeferredValue, useMemo } from 'react';
function DeferredSearch() {
const [input, setInput] = useState('');
const deferredInput = useDeferredValue(input);
// 使用延迟值进行昂贵的计算
const filteredList = useMemo(() => {
console.log('Filtering with:', deferredInput);
return heavyFilter(deferredInput);
}, [deferredInput]);
return (
<div>
{/* 输入框立即更新 */}
<input
value={input}
onChange={(e) => setInput(e.target.value)}
/>
{/* 列表使用延迟值,低优先级更新 */}
<ul>
{filteredList.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
// 显示过时内容
function SlowList({ text }) {
const deferredText = useDeferredValue(text);
const isStale = text !== deferredText;
return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{items.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
// ==================== 5. Suspense for Data Fetching ====================
import { Suspense } from 'react';
// 资源包装器
function wrapPromise(promise) {
let status = 'pending';
let result;
const suspender = promise.then(
(data) => {
status = 'success';
result = data;
},
(error) => {
status = 'error';
result = error;
}
);
return {
read() {
if (status === 'pending') {
throw suspender; // Suspense捕获
} else if (status === 'error') {
throw result;
}
return result;
}
};
}
const userResource = wrapPromise(fetchUser());
function User() {
const user = userResource.read(); // 如果pending,抛出promise
return <div>{user.name}</div>;
}
function AppWithSuspense() {
return (
<Suspense fallback={<div>Loading user...</div>}>
<User />
<Suspense fallback={<div>Loading posts...</div>}>
<Posts />
</Suspense>
</Suspense>
);
}
// 并行加载
function ProfilePage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<User />
<Posts />
{/* User和Posts并行加载 */}
</Suspense>
);
}
// 串行加载
function ProfilePageSerial() {
return (
<Suspense fallback={<div>Loading user...</div>}>
<User />
<Suspense fallback={<div>Loading posts...</div>}>
<Posts />
{/* Posts在User加载完成后才开始加载 */}
</Suspense>
</Suspense>
);
}
// ==================== 6. useId ====================
function LoginForm() {
const id = useId();
return (
<div>
<label htmlFor={`${id}-email`}>Email:</label>
<input id={`${id}-email`} type="email" />
<label htmlFor={`${id}-password`}>Password:</label>
<input id={`${id}-password`} type="password" />
</div>
);
}
// 多个实例有唯一ID
function App() {
return (
<>
<LoginForm /> {/* IDs: :r1:-email, :r1:-password */}
<LoginForm /> {/* IDs: :r2:-email, :r2:-password */}
</>
);
}
// ==================== 7. useSyncExternalStore ====================
import { useSyncExternalStore } from 'react';
// 订阅外部store(如Redux, MobX等)
function useStore(store) {
return useSyncExternalStore(
store.subscribe, // 订阅
store.getSnapshot, // 获取当前值
store.getServerSnapshot // 服务端渲染的快照(可选)
);
}
// 简单store实现
function createStore(initialState) {
let state = initialState;
const listeners = new Set();
return {
getSnapshot: () => state,
subscribe: (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
},
setState: (newState) => {
state = newState;
listeners.forEach(l => l());
}
};
}
const store = createStore({ count: 0 });
function Counter() {
const state = useSyncExternalStore(
store.subscribe,
store.getSnapshot
);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => store.setState({ count: state.count + 1 })}>
Increment
</button>
</div>
);
}
// 订阅浏览器API
function useOnlineStatus() {
return useSyncExternalStore(
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine,
() => true // 服务端默认在线
);
}
function OnlineStatus() {
const isOnline = useOnlineStatus();
return <div>{isOnline ? 'Online' : 'Offline'}</div>;
}
// ==================== 8. startTransition API ====================
import { startTransition } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleChange(e) {
setQuery(e.target.value);
// 非Hook版本的transition
startTransition(() => {
const filtered = search(e.target.value);
setResults(filtered);
});
}
return (
<div>
<input value={query} onChange={handleChange} />
<Results items={results} />
</div>
);
}
// ==================== 9. 性能对比 ====================
// React 17 vs React 18
// React 17:
// - 同步渲染,阻塞主线程
// - 部分批处理
// - 没有优先级概念
// React 18:
// - 并发渲染,可中断
// - 完全自动批处理
// - 紧急 vs 非紧急更新
// - 更流畅的用户体验
// 实际应用场景
function RealWorldExample() {
const [isPending, startTransition] = useTransition();
const [searchTerm, setSearchTerm] = useState('');
const [products, setProducts] = useState([]);
function handleSearch(e) {
const value = e.target.value;
// 紧急:立即更新输入框
setSearchTerm(value);
// 非紧急:搜索产品(可被中断)
startTransition(() => {
const filtered = searchProducts(value);
setProducts(filtered);
});
}
return (
<div>
<input
type="search"
value={searchTerm}
onChange={handleSearch}
placeholder="Search products..."
/>
{isPending ? (
<div>Searching...</div>
) : (
<ProductList products={products} />
)}
</div>
);
}
// 辅助组件
const HomePage = () => <div>Home Content</div>;
const PostsPage = () => <div>Posts Content</div>;
const ContactPage = () => <div>Contact Content</div>;
const Posts = () => <div>Posts</div>;
const ProductList = ({ products }) => (
<div>{products.map(p => <div key={p.id}>{p.name}</div>)}</div>
);
function searchProducts(query) {
// 搜索逻辑
return [];
}
🎓 总结和学习建议
React 18升级要点
-
更新到React 18
npm install react@18 react-dom@18
-
使用createRoot
// 旧
ReactDOM.render(<App />, container);// 新
const root = createRoot(container);
root.render(<App />); -
利用新特性
- 使用
useTransition优化重渲染 - 使用
Suspense优化加载体验 - 使用
useDeferredValue延迟非关键更新
面试重点
- 理解并发渲染的概念和好处
- 掌握Suspense的工作原理
- 熟悉useTransition的使用场景
- 了解自动批处理带来的性能提升
实践建议
- 在新项目中使用React 18
- 逐步将旧项目迁移到React 18
- 使用新特性优化用户体验
- 关注性能监控和优化
完整60题已全部完成! 🎉
这份完整的面试指南涵盖了:
- ✅ Java核心知识(30题)
- ✅ React基础到高级(30题)
- ✅ 每题都有详细解释和代码示例
- ✅ 实战技巧和最佳实践
建议你按照以下步骤学习:
- 每天5-10题,深入理解概念
- 动手实践每个代码示例
- 构建小项目巩固知识
- 模拟面试检验学习成果
祝你面试成功!💪🚀
🎯 面试准备建议
学习路线
- 基础巩固 (1-2周)
- Java基础语法
- React核心概念
- 深入学习 (2-3周)
- 集合框架、多线程
- Hooks、状态管理
- 项目实践 (2-3周)
- 构建完整的全栈应用
- 使用Spring Boot + React
- 面试刷题 (1-2周)
- LeetCode算法题
- 模拟面试
项目推荐
- Todo应用(全栈)
- 电商网站
- 社交媒体应用
- 博客系统
面试技巧
- STAR法则回答行为问题
- 先思考再回答,不要急于作答
- 举具体例子说明技术应用
- 主动提问展示你的兴趣
- 诚实面对不会的问题
额外资源
- 官方文档:Java Docs, React Docs
- 在线教程:freeCodeCamp, Udemy
- 技术博客:Medium, Dev.to
- GitHub项目:学习优秀开源项目
祝你面试成功!记住:准备充分 + 自信表达 = 成功! 💪
本文档包含60道精选面试题,涵盖Java和React的所有核心知识点。建议每天学习5-10道题,配合实际代码练习。