目录


一、React 底层原理

1.1 Fiber 架构深度解析

1.1.1 为什么需要 Fiber?(从 Stack Reconciler 到 Fiber Reconciler)

【架构演进】Fiber 是 React 16 最重要的架构升级,解决了同步渲染的阻塞问题

传统 Stack Reconciler(React 15 及之前):
┌─────────────────────────────────────┐
│  同步渲染:不可中断                    │
│                                     │
│  用户点击 → setState → 完整渲染 → 绘制 │
│                 ↓                    │
│           组件树深度遍历                │
│           (可能耗时 100ms+)           │
│                 ↓                    │
│      【阻塞主线程,用户感到卡顿】        │
└─────────────────────────────────────┘

Fiber Reconciler(React 16+):
┌─────────────────────────────────────┐
│  异步可中断渲染                        │
│                                     │
│  用户点击 → setState → 渲染(可中断)   │
│                 ↓                    │
│         分片执行,每帧 16ms            │
│         高优先级任务可打断              │
│                 ↓                    │
│      【保持流畅,用户交互不卡顿】        │
└─────────────────────────────────────┘
🔄 Fiber 的核心思想
// Fiber 节点数据结构(简化版)
const fiber = {
  // 节点标识
  type: 'div',                    // 组件类型
  key: null,                       // React key
  stateNode: DOMElement,            // 真实 DOM 引用

  // 链表结构(Fiber 的核心)
  child: fiber,                     // 第一个子节点
  sibling: fiber,                   // 下一个兄弟节点
  return: fiber,                    // 父节点

  // 渲染所需数据
  pendingProps: {},                // 新的 props
  memoizedProps: {},               // 缓存的 props
  memoizedState: {},               // 组件状态

  // 更新队列
  updateQueue: [],                 // 待处理的更新
  lanes: 0,                        // 优先级标记

  // 副作用标记
  flags: 0,                        // 副作用类型
  subtreeFlags: 0,                 // 子树副作用
};
📊 Fiber 链表遍历算法
// 深度优先遍历(DFS)改为【顺序遍历 + 可中断】
function beginWork(wip) {
  // 1. 创建子 Fiber 节点
  // 2. 根据 props/state 更新组件
  // 3. 返回第一个子节点继续处理
  switch (wip.tag) {
    case HostRoot:
      return updateHostRoot(wip);
    case HostComponent:
      return updateHostComponent(wip);
    case FunctionComponent:
      return updateFunctionComponent(wip);
    // ...
  }
}

// 可中断的工作循环
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

// shouldYield() 检查是否需要让出主线程
function shouldYield() {
  return getCurrentTime() >= deadline;
}
1.1.2 优先级与 Lane 模型

【性能关键】React 18 使用 Lane(车道)模型替代之前的优先级概念

// React 18 Lane 模型(简化)
const lanes = {
  // 同步车道
  SyncLane: 0b0001,

  // 连续交互车道(用户拖拽、输入等)
  InputContinuousLane: 0b0010,

  // 默认车道(数据获取、动画等)
  DefaultLane: 0b0100,

  // 过渡车道(startTransition 标记的任务)
  TransitionLane1: 0b1000,
  TransitionLane2: 0b10000,

  // 空闲车道(预加载等低优先级任务)
  IdleLane: 0b100000,
};

// 优先级排序(从高到低)
// SyncLane > InputContinuousLane > DefaultLane > TransitionLane > IdleLane
🎯 startTransition 的原理
// React 18 startTransition API
const App = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleSearch = (value) => {
    setQuery(value); // 紧急更新:立即响应

    // startTransition 标记的更新是【可中断的】
    startTransition(() => {
      // 这个更新可以被高优先级任务打断
      setResults(expensiveSearch(value));
    });
  };

  return (
    <div>
      <input value={query} onChange={(e) => handleSearch(e.target.value)} />
      <SearchResults results={results} />
    </div>
  );
};
1.1.3 Concurrent Mode(并发模式)

【React 18 新特性】并发渲染使得多个状态更新可以同时进行

// React 18 自动批处理(Automatic Batching)
// 之前:每个 setState 都触发一次重新渲染
// 现在:多个 setState 合并为一次渲染

const BadExample = () => {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  const handleClick = () => {
    fetchSomething().then(() => {
      // ❌ React 17 中这会触发 2 次渲染
      // ✅ React 18 中自动批处理,只触发 1 次渲染
      setCount(c => c + 1);
      setFlag(f => !f);
    });
  };

  return <button onClick={handleClick}>Click</button>;
};

// useTransition vs useDeferredValue
const SearchResults = ({ query }) => {
  // useTransition:标记非紧急更新
  const [isPending, startTransition] = useTransition();

  const [deferredQuery, setDeferredQuery] = useState(query);

  // useDeferredValue:延迟更新(类似防抖但更智能)
  const deferredQuery = useDeferredValue(query);

  return (
    <div style={{ opacity: isPending ? 0.5 : 1 }}>
      <Results query={deferredQuery} />
    </div>
  );
};

1.2 调和算法(Reconciliation)

1.2.1 调和算法核心思想

【原理】调和是 React 用来对比新旧 Virtual DOM 的算法,决定如何高效更新真实 DOM

┌─────────────────────────────────────────────────────┐
│                                                     │
│   setState()  ──→  生成新 Virtual DOM              │
│                        ↓                            │
│              【Reconciliation】                      │
│                        ↓                            │
│         对比新旧 Virtual DOM(Diff 算法)           │
│                        ↓                            │
│              计算出最小更新操作                      │
│                        ↓                            │
│              【Commit 阶段】更新真实 DOM            │
│                                                     │
└─────────────────────────────────────────────────────┘
1.2.2 Diff 算法的三个核心假设

【性能关键】这三条假设使得 O(n³) → O(n) 的复杂度成为可能

假设一:不同类型的元素产生不同的树
// 当元素类型不同时,React 会【完全替换】整棵子树
// 不再比较子节点,直接销毁旧树,创建新树

// 旧树
<div className="before">
  <ComponentA />
  <ComponentB />
</div>

// 新树(类型不同,整棵子树被替换)
<span className="after">
  <ComponentA />
  <ComponentB />
</span>

// React 行为:
// 1. 卸载 <div> 及其所有子组件(触发 componentWillUnmount)
// 2. 创建新的 <span> 和子组件(触发 componentDidMount)
// 【性能开销】很大,但保证了正确性
假设二:通过 key 来标识同级兄弟元素
// ✅ 好的 key:稳定、唯一、可预测
const GoodList = ({ users }) => (
  <ul>
    {users.map(user => (
      <li key={user.id}>{user.name}</li>  // 使用数据库 ID
    ))}
  </ul>
);

// ❌ 坏的 key:索引、随机值
const BadList = ({ users }) => (
  <ul>
    {users.map((user, index) => (
      <li key={index}>{user.name}</li>  // 插入/删除会导致错误匹配
    ))}
  </ul>
);

// ⚠️ 使用 Math.random() 作为 key 是最糟糕的做法
// 每次渲染 key 都不同,React 会认为所有元素都是新的
const TerribleList = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={Math.random()}>{item.name}</li>  // 💀 永远不要这样做
    ))}
  </ul>
);
假设三:只同层对比,不跨层级移动
旧树:                    新树:
<div>                    <div>
  <p>                      <p>
    <span>A</span>    →      <span>A</span> ✅ 同位置,保留
  </p>                     </p>
  <p>                      <p>
    <span>B</span>    →      <span>C</span> ⚠️ 同位置但内容变了,更新
  </p>                     </p>
                          <p>
                            <span>D</span> ✨ 新增
                          </p>
</div>                    </div>

// React 不会尝试跨层级移动元素
// 如果要移动,需要完全卸载并重新挂载
1.2.3 Diff 算法源码解析
// React 源码简化版:reconcileChildrenArray
function reconcileChildrenArray(
  returnFiber,
  currentFirstChild,
  newChildren
) {
  // 1. 首先遍历新数组,为每个元素创建或复用 Fiber
  let resultingFirstChild = null;
  let prevDiffFiber = null;
  let newIdx = 0;

  // 第一轮:尝试复用已有的 Fiber(基于 key)
  for (; newIdx < newChildren.length; newIdx++) {
    const newFiber = createFiber(newChildren[newIdx], returnFiber);

    if (newFiber.key === null) {
      // 无 key 的元素,尝试用索引匹配
      // ⚠️ 这就是为什么无 key 列表性能较差
    }

    // 复用或创建 Fiber
    const matchedFiber = reconcileChildFibers(
      returnFiber,
      currentFirstChild,
      newFiber
    );

    if (matchedFiber === null) break;

    // 更新链表
    if (prevDiffFiber === null) {
      resultingFirstChild = matchedFiber;
    } else {
      prevDiffFiber.sibling = matchedFiber;
    }
    prevDiffFiber = matchedFiber;
  }

  // 2. 处理剩余的旧 Fiber(需要删除)
  if (oldFiber !== null) {
    deleteChild(returnFiber, oldFiber);
  }

  return resultingFirstChild;
}

1.3 Hooks 底层实现原理

1.3.1 Hooks 链表设计

【核心原理】React 通过链表保存 Hooks 的状态,每个 Hook 节点包含 [state, setState]

// 函数组件的 Hooks 链表结构
const FunctionComponentFiber = {
  // ... 其他字段

  // 【关键】保存 Hooks 状态的链表
  memoizedState: {
    // Hook 1: useState
    baseState: 0,                      // 基础状态值
    queue: { pending: null },          // 更新队列
    next: {
      // Hook 2: useState
      baseState: '',
      queue: { pending: null },
      next: {
        // Hook 3: useEffect
        baseState: null,
        queue: { pending: null },
        next: null                     // 链表结束
      }
    }
  }
};

// 首次渲染:按顺序创建链表
function useState(initialState) {
  // 按顺序获取当前 hook(首次创建,复用时读取)
  const hook = workInProgressHook();

  if (workInProgressHook === null) {
    // 首次渲染:初始化
    hook.baseState = typeof initialState === 'function'
      ? initialState()
      : initialState;
  }

  // 返回 [state, setState]
  return [hook.baseState, dispatchAction.bind(null, hook.queue)];
}

// 更新渲染:遍历链表获取已有状态
function dispatchAction(hook.queue, action) {
  // 创建更新对象,加入队列
  const update = { action, next: null };
  // ... 合并到环形链表

  // 标记需要重新渲染
  scheduleUpdateOnFiber(currentFiber);
}
1.3.2 useState 完整实现解析
// React 源码:useState 实现
function useState(initialState) {
  const hook = mountWorkInProgressHook();

  if (typeof initialState === 'function') {
    hook.memoizedState = initialState();
  } else {
    hook.memoizedState = initialState;
  }

  const queue = {
    pending: null,
    dispatch: dispatchSetState.bind(null, currentlyRenderingFiber, queue),
  };

  hook.queue = queue;

  const dispatch = queue.dispatch;

  return [hook.memoizedState, dispatch];
}

// dispatchSetState:更新状态
function dispatchSetState(fiber, queue, action) {
  const lane = requestUpdateLane(fiber);
  const update = {
    action,
    lane,
    next: null,
  };

  // 加入环形队列
  enqueueUpdate(fiber, queue, update);

  // 调度更新(异步可中断)
  scheduleUpdateOnFiber(fiber, lane);
}

// 状态计算:使用环形链表合并所有更新
function updateReducer(reducer, initialState) {
  const hook = workInProgressHook;

  let newState = hook.memoizedState;

  if (hook.baseQueue !== null) {
    const firstBaseUpdate = hook.baseQueue.next;
    let newBaseUpdate = firstBaseUpdate;

    do {
      // 处理每个更新
      if (newBaseUpdate.action instanceof Function) {
        newState = newBaseUpdate.action(newState);
      } else {
        newState = newBaseUpdate.action;
      }
      newBaseUpdate = newBaseUpdate.next;
    } while (newBaseUpdate !== firstBaseUpdate);
  }

  hook.memoizedState = newState;
  return [newState, hook.queue.dispatch];
}
1.3.3 useEffect 的实现原理
// React 源码:useEffect 实现
function useEffect(create, deps) {
  const hook = mountWorkInProgressHook();

  let nextDeps = deps === undefined ? null : deps;

  if (currentHook !== null) {
    const prevEffect = currentHook.memoizedState;
    const prevDeps = prevEffect.deps;

    // 【关键】依赖比较:浅比较
    if (areHookInputsEqual(nextDeps, prevDeps)) {
      // 依赖没变,跳过此次 effect
      return;
    }
  }

  // 依赖变了,添加 effect 到链表
  hook.memoizedState = pushEffect(
    HookHasEffect | HookPassive,  // flags
    create,
    nextDeps
  );
}

// pushEffect:创建副作用节点
function pushEffect(tag, create, deps) {
  const effect = {
    tag,
    create,
    deps,
    next: null,
  };

  if (componentUpdateQueue === null) {
    componentUpdateQueue = { lastEffect: null };
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    const lastEffect = componentUpdateQueue.lastEffect;
    effect.next = lastEffect.next;
    lastEffect.next = effect;
    componentUpdateQueue.lastEffect = effect;
  }

  return effect;
}

// 提交阶段:执行所有 effects
function commitPassiveEffects(root, lanes) {
  // 遍历所有 Passive effect
  // 调用 create() 返回的清理函数
  // 调用新的 create()
}
1.3.4 闭包陷阱与依赖项规则

【高频面试点】理解闭包陷阱才能正确使用 useEffect

⚠️ 经典闭包陷阱案例
// ❌ 陷阱 1:setInterval 中的闭包
const BadTimer = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // count 永远是首次渲染的值(0)!
    }, 1000);

    return () => clearInterval(id);
  }, []); // 空依赖,effect 只执行一次

  return <div>{count}</div>; // 永远只会显示 1
};

// ✅ 解决方案 1:函数式更新
const GoodTimer1 = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // 使用函数式更新,始终基于最新值
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <div>{count}</div>;
};

// ✅ 解决方案 2:使用 useRef
const GoodTimer2 = () => {
  const [count, setCount] = useState(0);
  const countRef = useRef(0); // 可变引用,不触发重新渲染

  useEffect(() => {
    const id = setInterval(() => {
      countRef.current += 1;
      setCount(countRef.current);
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <div>{count}</div>;
};

// ❌ 陷阱 2:useEffect 依赖遗漏
const BadEffect = ({ userId }) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // 使用了 userId 但未加入依赖
    fetchUser(userId).then(setUser);
  }, []); // ⚠️ 缺少 [userId]!

  // userId 变化时不会重新获取数据

  return <div>{user?.name}</div>;
};

// ✅ 正确写法
const GoodEffect = ({ userId }) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    let isCancelled = false; // 防止卸载后 setState

    fetchUser(userId).then(data => {
      if (!isCancelled) setUser(data);
    });

    return () => {
      isCancelled = true; // 清理函数
    };
  }, [userId]); // ✅ 包含所有使用的外部变量

  return <div>{user?.name}</div>;
};
🔧 ESLint exhaustive-deps 规则
// .eslintrc 配置
{
  "rules": {
    "react-hooks/exhaustive-deps": [
      "error",
      {
        "additionalHooks": "(useMyCustomHook)" // 注册自定义 hooks
      }
    ]
  }
}

// ⚠️ 这个规则会自动检测所有 useEffect 的依赖
// 必须严格遵守,否则可能出现:
// 1. 数据过时(stale data)
// 2. 内存泄漏
// 3. 无限循环

1.4 合成事件机制

1.4.1 合成事件 vs 原生事件

【架构设计】React 为了跨平台和性能,设计了合成事件系统

┌─────────────────────────────────────────────────────┐
│                                                     │
│   原生事件(Native Events)                          │
│   ├── click        ── DOM Level 3 Events            │
│   ├── focus        ── FocusEvent                    │
│   ├── submit       ── Event                        │
│   └── touchstart   ── TouchEvent                    │
│                                                     │
│   合成事件(SyntheticEvent)                         │
│   ├── onClick      ── SyntheticEvent                │
│   ├── onFocus      ── SyntheticFocusEvent           │
│   ├── onSubmit     ── SyntheticEvent                │
│   └── onTouchStart ── SyntheticTouchEvent           │
│                                                     │
└─────────────────────────────────────────────────────┘
1.4.2 事件绑定原理
// React 17+ 的事件委托架构
const root = document.getElementById('root');

// React 16:所有事件绑定到根容器
root.attachEventListener('onclick', ReactEventListener);

// React 17+:所有事件绑定到根 DOM 节点
// 好处:多个 React 版本共存时事件不冲突
root.addEventListener('click', ReactEventListener, false);

// ReactEventListener 内部
function ReactEventListener() {
  // 1. 从事件源向上遍历 DOM 树
  // 2. 收集所有绑定了同一事件的 React 组件
  // 3. 按照【冒泡/捕获】顺序执行
  // 4. 构建 synthetic event 传递给每个组件
}

// React 给 DOM 元素添加的所有事件监听
const nativeEventToReactEvent = {
  'click': 'onClick',
  'mousedown': 'onMouseDown',
  'touchstart': 'onTouchStart',
  'change': 'onChange',       // 特殊处理
  'input': 'onInput',          // 特殊处理
};
1.4.3 事件池与性能优化
// React 17 之前:事件对象被【池化】复用
// 事件处理完成后,对象被归还到池中,属性被清空

// ❌ 这段代码在 React 16 有问题
const BadComponent = () => {
  const handleClick = (e) => {
    setTimeout(() => {
      console.log(e.target.value); // ⚠️ 事件对象已被回收!
    }, 0);
  };

  return <button onClick={handleClick}>Click</button>;
};

// ✅ React 17+ 不再使用事件池
// 事件对象会一直保留,直到所有异步操作完成
const GoodComponent = () => {
  const handleClick = (e) => {
    const value = e.target.value; // ✅ 安全地保存值
    setTimeout(() => {
      console.log(value);
    }, 0);
  };

  return <button onClick={handleClick}>Click</button>;
};
1.4.4 常见事件问题处理
// 1. 事件冒泡与阻止
const EventHandling = () => {
  const handleChildClick = (e) => {
    e.stopPropagation(); // ✅ 阻止冒泡
    console.log('Child clicked');
  };

  const handleParentClick = () => {
    console.log('Parent clicked'); // 不会触发
  };

  return (
    <div onClick={handleParentClick}>
      <button onClick={handleChildClick}>Click me</button>
    </div>
  );
};

// 2. 事件捕获(从外到内)
const CaptureEvent = () => (
  <div onClickCapture={handleOuter}>
    <div onClickCapture={handleMiddle}>
      <button onClick={handleInner}>Click</button>
    </div>
  </div>
);
// 点击按钮时执行顺序:handleOuter → handleMiddle → handleInner

// 3. 键盘事件
const KeyboardInput = () => {
  const handleKeyDown = (e) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed!');
    }
    if (e.ctrlKey && e.key === 's') {
      e.preventDefault(); // 阻止默认行为(保存快捷键)
      saveDocument();
    }
  };

  return (
    <input
      onKeyDown={handleKeyDown}
      placeholder="Press Enter or Ctrl+S"
    />
  );
};

// 4. 表单事件(与原生行为的区别)
const FormEvents = () => {
  const handleSubmit = (e) => {
    e.preventDefault(); // ✅ React 中必须手动阻止默认行为
  };

  const handleChange = (e) => {
    const { name, value } = e.target; // ✅ 获取表单值
    console.log(`${name}: ${value}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="username" onChange={handleChange} />
      <button type="submit">Submit</button>
    </form>
  );
};

二、高级 Hooks & 组件复用

2.1 进阶 Hooks 深度应用

2.1.1 useMemo —— 计算结果缓存

【性能优化】避免每次渲染都重新计算昂贵的数据

// ✅ 场景 1:避免重复计算
const ExpensiveCalculation = ({ data, filter }) => {
  // ❌ 每次渲染都重新计算(data 变化时)
  const processedData = data
    .filter(item => item.type === filter)
    .sort((a, b) => a.value - b.value)
    .map(item => ({ ...item, computed: item.value * 1.5 });

  // ✅ 使用 useMemo:仅在 data 或 filter 变化时重新计算
  const processedData = useMemo(() => {
    console.log('Running expensive calculation...'); // 用于观察
    return data
      .filter(item => item.type === filter)
      .sort((a, b) => a.value - b.value)
      .map(item => ({ ...item, computed: item.value * 1.5 }));
  }, [data, filter]); // 依赖数组

  return <DataList data={processedData} />;
};

// ✅ 场景 2:稳定对象引用(避免子组件重渲染)
const Parent = ({ a, b }) => {
  const [count, setCount] = useState(0);

  // ❌ 每次渲染都创建新对象,导致 MemoizedChild 重渲染
  const config = { padding: 10, margin: a };

  // ✅ useMemo 缓存对象引用
  const config = useMemo(() => ({
    padding: 10,
    margin: a,
  }), [a]);

  return (
    <>
      <MemoizedChild config={config} />
      <button onClick={() => setCount(c => c + 1)}>Re-render: {count}</button>
    </>
  );
};

const MemoizedChild = React.memo(({ config }) => {
  console.log('MemoizedChild rendered'); // 观察重渲染
  return <div style={config}>{config.margin}</div>;
});

// ⚠️ 不要过度使用 useMemo:
// - 没有明显的性能问题时,useMemo 可能反而增加开销
// - 简单的计算(如 a + b)不需要 useMemo
// - 依赖数组为空时等价于 componentDidMount(基本不用)
2.1.2 useCallback —— 回调函数缓存

【最佳拍档】与 React.memo 配合使用,防止不必要的重渲染

// ✅ useCallback = useMemo + 函数封装
// useCallback(fn, deps) 等价于 useMemo(() => fn, deps)

// 场景:传递给子组件的回调函数
const Parent = ({ userId }) => {
  const [data, setData] = useState(null);

  // ❌ 每次渲染都创建新函数,子组件会重渲染
  const handleSelect = (item) => {
    console.log('Selected:', item);
    setData(item);
  };

  // ✅ useCallback 缓存函数引用
  const handleSelect = useCallback((item) => {
    console.log('Selected:', item);
    setData(item);
  }, []); // 依赖为空,函数引用始终稳定

  // ✅ 结合 React.memo 实现精准渲染控制
  return <List items={items} onSelect={handleSelect} />;
};

const List = React.memo(({ items, onSelect }) => {
  console.log('List rendered');
  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => onSelect(item)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

// ⚠️ useCallback 也有开销,不要滥用
// 简单场景下可能适得其反
const SimpleButton = ({ onClick }) => {
  // ❌ 过度优化:普通按钮组件不需要 useCallback
  const handleClick = useCallback(() => {
    onClick();
  }, [onClick]);

  return <button onClick={handleClick}>Click</button>;
};
📊 useMemo vs useCallback 对比
Hook 缓存内容 典型场景 注意事项
useMemo 计算结果(值) 昂贵计算、对象/数组引用 不要用于简单计算
useCallback 函数引用 传递给子组件的回调 配合 React.memo 使用
2.1.3 useLayoutEffect —— 同步执行副作用

【使用场景】DOM 操作后立即同步更新视觉

// ⚠️ 与 useEffect 的区别
// useEffect:异步执行,渲染后再执行(可能导致闪烁)
// useLayoutEffect:同步执行,DOM 变更后立即执行(阻塞绘制)

// ✅ useLayoutEffect 场景:获取 DOM 尺寸、焦点管理、滚动位置
const Tooltip = ({ targetRef, children }) => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useLayoutEffect(() => {
    // ✅ DOM 已经更新,但还未绘制
    // 同步计算位置,避免闪烁
    const rect = targetRef.current.getBoundingClientRect();
    setPosition({
      x: rect.right,
      y: rect.top,
    });
  }, [targetRef]);

  return (
    <div style={{ position: 'absolute', left: position.x, top: position.y }}>
      {children}
    </div>
  );
};

// ✅ useEffect 场景:数据获取、订阅
const DataFetcher = ({ url }) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 异步获取数据,不阻塞渲染
    fetch(url).then(res => res.json()).then(setData);
  }, [url]);

  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
};

// ⚠️ SSR 时的警告
const Component = () => {
  useLayoutEffect(() => {
    // React 17 及之前:SSR 时会报错
    // React 18+:会在 useEffect 之前执行,但仍需注意
  }, []);

  return <div>Content</div>;
};

// ✅ 解决方案:添加防闪烁处理
const SSR-safeComponent = ({ serverData }) => {
  const [clientData, setClientData] = useState(null);

  useLayoutEffect(() => {
    // 仅在客户端执行
    setClientData(computeClientSide(serverData));
  }, [serverData]);

  return <div>{clientData ?? serverData}</div>; // 避免 hydration 不匹配
};
2.1.4 useId —— 生成稳定唯一 ID

【React 18 新增】解决 SSR/Hydration 时 ID 不稳定的问题

// ❌ 旧方案:使用 Math.random() 或 index(SSR 不稳定)
const OldInput = ({ label }) => {
  const id = Math.random().toString(36); // 💀 每次渲染都不同!

  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </div>
  );
};

// ✅ 新方案:useId(React 18+)
const NewInput = ({ label }) => {
  const id = useId(); // ✅ 稳定、唯一、在 SSR 中也能保持一致

  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </div>
  );
};

// ✅ 多个 ID 的场景(使用共享前缀)
const MultipleInputs = () => {
  const id = useId();

  return (
    <div>
      <input id={`${id}-name`} />
      <input id={`${id}-email`} />
      <input id={`${id}-phone`} />
    </div>
  );
};

// ✅ 与表单库配合(react-hook-form)
const FormWithHookForm = () => {
  const { register } = useForm();
  const id = useId();

  return (
    <form>
      <input {...register('name')} id={`${id}-name`} />
      <label htmlFor={`${id}-name`}>Name</label>
    </form>
  );
};
2.1.5 useTransition 与 useDeferredValue

【React 18 并发特性】处理非紧急更新

// ✅ useTransition:标记非紧急更新
const SearchPage = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (value) => {
    setQuery(value); // 紧急:立即更新输入框

    // startTransition 标记的更新是非紧急的
    startTransition(() => {
      // 这个更新可以被高优先级任务打断
      setResults(expensiveSearch(value));
    });
  };

  return (
    <div>
      <input value={query} onChange={e => handleSearch(e.target.value)} />
      {isPending ? <Spinner /> : <Results results={results} />}
    </div>
  );
};

// ✅ useDeferredValue:延迟更新(类似防抖但更智能)
const SearchResults = ({ query }) => {
  // deferredQuery 会"落后"于 query
  // 当 query 快速变化时,UI 仍能保持响应
  const deferredQuery = useDeferredValue(query);

  const isStale = query !== deferredQuery;

  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      <Results results={expensiveSearch(deferredQuery)} />
    </div>
  );
};

// ✅ 典型场景:大列表搜索
const BigList = ({ items, filter }) => {
  const deferredFilter = useDeferredValue(filter);

  // 即使 filter 快速变化,列表也能平滑渲染
  const filteredItems = useMemo(() => {
    return items.filter(item =>
      item.name.includes(deferredFilter)
    );
  }, [items, deferredFilter]);

  return (
    <VirtualList items={filteredItems} />
  );
};

2.2 自定义 Hooks 设计模式

2.2.1 自定义 Hook 核心原则

【复用逻辑】自定义 Hook 是 React 中逻辑复用的最佳方式

自定义 Hook 命名规则:
✅ use + 业务逻辑/功能名
   useAuth, useCart, useDebounce, useFetch

❌ 不要用
   withPermission (这是 HOC 的命名)
   renderUserList (这是组件的命名)
🎯 基础自定义 Hook 模式
// 场景 1:封装数据获取逻辑
const useFetch = (url, options) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    const fetchData = async () => {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch(url, options);
        if (!response.ok) throw new Error(`HTTP ${response.status}`);

        const json = await response.json();

        // 防止组件卸载后 setState
        if (!cancelled) {
          setData(json);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };

    fetchData();

    return () => {
      cancelled = true; // 清理函数
    };
  }, [url]); // 依赖 url

  return { data, loading, error, refetch: () => fetchData() };
};

// 使用
const UserProfile = ({ userId }) => {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <Skeleton />;
  if (error) return <Error message={error} />;

  return <Profile user={user} />;
};
🎯 进阶自定义 Hook:表单处理
// 封装复杂表单逻辑
const useForm = (initialValues, validate, onSubmit) => {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = (field) => (e) => {
    const { value } = e.target;
    setValues(prev => ({ ...prev, [field]: value }));

    // 实时校验
    if (touched[field]) {
      const error = validate({ [field]: value })[field];
      setErrors(prev => ({ ...prev, [field]: error }));
    }
  };

  const handleBlur = (field) => () => {
    setTouched(prev => ({ ...prev, [field]: true }));

    const error = validate(values)[field];
    setErrors(prev => ({ ...prev, [field]: error }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    // 标记所有字段为已触摸
    const allTouched = Object.keys(values).reduce(
      (acc, key) => ({ ...acc, [key]: true }),
      {}
    );
    setTouched(allTouched);

    // 验证所有字段
    const validationErrors = validate(values);
    setErrors(validationErrors);

    // 如果有错误,不提交
    if (Object.values(validationErrors).some(Boolean)) {
      return;
    }

    setIsSubmitting(true);
    try {
      await onSubmit(values);
    } finally {
      setIsSubmitting(false);
    }
  };

  const reset = () => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
  };

  return {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit,
    reset,
  };
};

// 使用
const LoginForm = () => {
  const {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit,
  } = useForm(
    { email: '', password: '' },
    validateForm,
    async (data) => {
      await api.login(data);
      toast.success('登录成功!');
    }
  );

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        value={values.email}
        onChange={handleChange('email')}
        onBlur={handleBlur('email')}
      />
      {touched.email && errors.email && <span>{errors.email}</span>}

      <input
        name="password"
        type="password"
        value={values.password}
        onChange={handleChange('password')}
        onBlur={handleBlur('password')}
      />
      {touched.password && errors.password && <span>{errors.password}</span>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '登录中...' : '登录'}
      </button>
    </form>
  );
};
2.2.2 常用工具 Hooks 集合
// useDebounce:防抖
const useDebounce = (value, delay = 300) => {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
};

// useLocalStorage:持久化状态
const useLocalStorage = (key, initialValue) => {
  const [value, setValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setStoredValue = useCallback((newValue) => {
    setValue(prev => {
      const valueToStore = newValue instanceof Function
        ? newValue(prev)
        : newValue;

      try {
        localStorage.setItem(key, JSON.stringify(valueToStore));
      } catch (error) {
        console.error('localStorage error:', error);
      }

      return valueToStore;
    });
  }, [key]);

  return [value, setStoredValue];
};

// usePrevious:获取上一个渲染的值
const usePrevious = (value) => {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
};

// useClickOutside:检测点击外部
const useClickOutside = (ref, handler) => {
  useEffect(() => {
    const listener = (event) => {
      if (!ref.current || ref.current.contains(event.target)) {
        return;
      }
      handler(event);
    };

    document.addEventListener('click', listener);

    return () => document.removeEventListener('click', listener);
  }, [ref, handler]);
};

// useMediaQuery:响应式监听
const useMediaQuery = (query) => {
  const [matches, setMatches] = useState(
    () => window.matchMedia(query).matches
  );

  useEffect(() => {
    const mediaQuery = window.matchMedia(query);

    const handler = (event) => setMatches(event.matches);
    mediaQuery.addEventListener('change', handler);

    return () => mediaQuery.removeEventListener('change', handler);
  }, [query]);

  return matches;
};

// 使用
const ResponsiveMenu = () => {
  const isMobile = useMediaQuery('(max-width: 768px)');
  const [isOpen, setIsOpen] = useState(false);
  const menuRef = useRef();

  useClickOutside(menuRef, () => setIsOpen(false));

  return isMobile ? (
    <MobileMenu ref={menuRef} isOpen={isOpen} />
  ) : (
    <DesktopMenu />
  );
};

2.3 组件复用模式

2.3.1 高阶组件(HOC)

【历史模式】曾是最流行的逻辑复用方式,现在推荐使用自定义 Hooks

// ❌ HOC 示例(了解即可,不再推荐)
// withLoading.ts
const withLoading = (Component) => {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) {
      return <Skeleton />;
    }
    return <Component {...props} />;
  };
};

// 使用
const UserListWithLoading = withLoading(UserList);

// 问题:
// 1. 层层嵌套,调试困难(DevTools 显示为 XXX_withLoading_XXX_withAuth_...)
// 2. Props 命名可能冲突
// 3. 多个 HOC 组合时顺序可能影响结果
2.3.2 Render Props

【过渡方案】通过 props 传递渲染函数的模式

// ✅ Render Props 示例
const MouseTracker = ({ render }) => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  return (
    <div onMouseMove={handleMouseMove}>
      {render(position)}
    </div>
  );
};

// 使用
const App = () => (
  <MouseTracker
    render={({ x, y }) => (
      <div>
        鼠标位置:{x}, {y}
      </div>
    )}
  />
);

// ✅ children 模式(更简洁)
const MouseTrackerChildren = ({ children }) => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  return (
    <div onMouseMove={(e) => setPosition({ x: e.clientX, y: e.clientY })}>
      {children(position)}
    </div>
  );
};

const App = () => (
  <MouseTrackerChildren>
    {({ x, y }) => <div>鼠标位置:{x}, {y}</div>}
  </MouseTrackerChildren>
);
2.3.3 组合式组件(Compound Components)

【现代最佳实践】通过隐式状态共享实现灵活的组件组合

// ✅ 组合式组件:折叠面板
const AccordionContext = createContext(null);

const Accordion = ({ children, defaultOpen = null }) => {
  const [openId, setOpenId] = useState(defaultOpen);

  const toggle = useCallback((id) => {
    setOpenId(prev => prev === id ? null : id);
  }, []);

  const value = useMemo(() => ({ openId, toggle }), [openId, toggle]);

  return (
    <AccordionContext.Provider value={value}>
      <div className="accordion">{children}</div>
    </AccordionContext.Provider>
  );
};

Accordion.Item = ({ id, children }) => {
  const { openId, toggle } = useContext(AccordionContext);
  const isOpen = openId === id;

  return (
    <div className="accordion-item">
      <Accordion.Header
        isOpen={isOpen}
        onClick={() => toggle(id)}
      >
        {children[0]}
      </Accordion.Header>
      <Accordion.Content isOpen={isOpen}>
        {children[1]}
      </Accordion.Content>
    </div>
  );
};

Accordion.Header = ({ children, isOpen, onClick }) => (
  <div className="accordion-header" onClick={onClick}>
    {children}
    <span>{isOpen ? '▲' : '▼'}</span>
  </div>
);

Accordion.Content = ({ children, isOpen }) => (
  <div className={`accordion-content ${isOpen ? 'open' : ''}`}>
    {children}
  </div>
);

// 使用
const App = () => (
  <Accordion defaultOpen="section1">
    <Accordion.Item id="section1">
      {['第一部分标题', '第一部分内容...']}
    </Accordion.Item>
    <Accordion.Item id="section2">
      {['第二部分标题', '第二部分内容...']}
    </Accordion.Item>
  </Accordion>
);

// ✅ 另一个例子:Tabs 组件
const Tabs = ({ children, defaultTab }) => {
  const [activeTab, setActiveTab] = useState(defaultTab);

  const value = useMemo(() => ({ activeTab, setActiveTab }), [activeTab]);

  return (
    <TabsContext.Provider value={value}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
};

Tabs.List = ({ children }) => (
  <div className="tabs-list" role="tablist">
    {children}
  </div>
);

Tabs.Tab = ({ id, children }) => {
  const { activeTab, setActiveTab } = useContext(TabsContext);

  return (
    <button
      role="tab"
      aria-selected={activeTab === id}
      onClick={() => setActiveTab(id)}
    >
      {children}
    </button>
  );
};

Tabs.Panel = ({ id, children }) => {
  const { activeTab } = useContext(TabsContext);

  if (activeTab !== id) return null;

  return (
    <div role="tabpanel">
      {children}
    </div>
  );
};

// 使用
const App = () => (
  <Tabs defaultTab="tab1">
    <Tabs.List>
      <Tabs.Tab id="tab1">标签一</Tabs.Tab>
      <Tabs.Tab id="tab2">标签二</Tabs.Tab>
    </Tabs.List>
    <Tabs.Panel id="tab1">内容一</Tabs.Panel>
    <Tabs.Panel id="tab2">内容二</Tabs.Panel>
  </Tabs>
);
📊 组件复用模式对比
模式 灵活性 易用性 调试难度 推荐度
HOC ⭐⭐⭐ ⭐⭐⭐ ⭐⭐ ❌ 不推荐
Render Props ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐
组合式组件 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ ✅ 强烈推荐
自定义 Hooks ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ ✅ 首选(逻辑复用)

三、状态管理 & 数据层

3.1 现代化状态管理方案

3.1.1 状态管理选型矩阵

【架构决策】根据项目规模和复杂度选择合适的方案

方案 适用场景 学习曲线 Bundle Size 状态规范化 推荐度
useState + useReducer 局部/模块状态 0KB ⭐⭐⭐⭐⭐
Context API 全局共享状态 0kb ⭐⭐⭐⭐
Zustand 中大型应用 ~3kb ⭐⭐⭐⭐⭐
Jotai 原子化状态 ~2kb ⭐⭐⭐⭐
Redux Toolkit 大型企业应用 ~10kb ✅✅ ⭐⭐⭐⭐
Recoil 实验性/特殊场景 ~30kb ⭐⭐⭐
3.1.2 Zustand —— 极简但强大

【推荐首选】API 简洁,TypeScript 支持好,性能优秀

// store/counter.ts
import { create } from 'zustand';
import { devtools, subscribeWithSelector } from 'zustand/middleware';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

export const useCounterStore = create<CounterState>()(
  devtools(
    subscribeWithSelector((set) => ({
      count: 0,

      increment: () => set((state) => ({ count: state.count + 1 })),

      decrement: () => set((state) => ({ count: state.count - 1 })),

      reset: () => set({ count: 0 }),
    })),
    { name: 'CounterStore' }
  )
);

// 高级用法:split slices
// store/userStore.ts
import { create } from 'zustand';

interface UserState {
  user: User | null;
  isLoading: boolean;
  error: string | null;
  setUser: (user: User | null) => void;
  fetchUser: (id: string) => Promise<void>;
}

export const useUserStore = create<UserState>((set, get) => ({
  user: null,
  isLoading: false,
  error: null,

  setUser: (user) => set({ user }),

  fetchUser: async (id) => {
    set({ isLoading: true, error: null });

    try {
      const response = await api.getUser(id);
      set({ user: response.data, isLoading: false });
    } catch (error) {
      set({ error: error.message, isLoading: false });
    }
  },
}));

// 在组件中使用
const Counter = () => {
  const { count, increment } = useCounterStore();

  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
};

// Selector 优化:避免不必要的重渲染
const ExpensiveComponent = () => {
  // ❌ 每次 count 变化都会重渲染
  // const { count, increment } = useCounterStore();

  // ✅ 只有 count 变化时才重渲染
  const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);

  return <div>{/* ... */}</div>;
};

// 订阅特定变化
useCounterStore.subscribe(
  (state) => state.count,
  (count, previousCount) => {
    console.log(`Count changed: ${previousCount} → ${count}`);
  }
);
3.1.3 Jotai —— 原子化状态管理

【原子化思想】每个状态都是独立的原子,组件按需订阅

// atoms/index.ts
import { atom } from 'jotai';

// 基础原子
const countAtom = atom(0);
const userAtom = atom<User | null>(null);

// 派生原子(类似 useMemo)
const doubledCountAtom = atom((get) => get(countAtom) * 2);

// 带 write 的原子
const incrementAtom = atom(
  (get) => get(countAtom),
  (get, set) => {
    set(countAtom, get(countAtom) + 1);
  }
);

// 异步原子
const userDataAtom = atom(async (get) => {
  const userId = get(userIdAtom);
  if (!userId) return null;
  return fetchUser(userId);
});

// 在组件中使用
import { useAtom } from 'jotai';

const Counter = () => {
  // 订阅单个原子
  const [count] = useAtom(countAtom);
  const [, increment] = useAtom(incrementAtom);

  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
};

// 派生状态
const Display = () => {
  const [doubled] = useAtom(doubledCountAtom);
  return <div>Doubled: {doubled}</div>;
};

// Jotai + React Query 配合
import { useAtom } from 'jotai';
import { atomWithQuery } from 'jotai/query';

const userQueryAtom = atomWithQuery((get) => ({
  queryKey: ['user', get(userIdAtom)],
  queryFn: async ({ queryKey }) => {
    const [, userId] = queryKey;
    return fetchUser(userId);
  },
}));

const UserProfile = () => {
  const [{ data, isLoading }] = useAtom(userQueryAtom);
  // ...
};
3.1.4 Redux Toolkit(大型项目标准)

【企业级方案】适合大型团队、复杂状态、需要 DevTools 调试的场景

// store/slices/userSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';

interface UserState {
  user: User | null;
  isLoading: boolean;
  error: string | null;
}

const initialState: UserState = {
  user: null,
  isLoading: false,
  error: null,
};

export const fetchUserById = createAsyncThunk(
  'user/fetchById',
  async (userId: string, { rejectWithValue }) => {
    try {
      const response = await api.getUser(userId);
      return response.data;
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUser: (state, action: PayloadAction<User | null>) => {
      state.user = action.payload;
    },
    updateUserName: (state, action: PayloadAction<string>) => {
      if (state.user) {
        state.user.name = action.payload;
      }
    },
    clearError: (state) => {
      state.error = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUserById.pending, (state) => {
        state.isLoading = true;
        state.error = null;
      })
      .addCase(fetchUserById.fulfilled, (state, action) => {
        state.isLoading = false;
        state.user = action.payload;
      })
      .addCase(fetchUserById.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      });
  },
});

export const { setUser, updateUserName, clearError } = userSlice.actions;
export default userSlice.reducer;

// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';

import userReducer from './slices/userSlice';
import cartReducer from './slices/cartSlice';

export const store = configureStore({
  reducer: {
    user: userReducer,
    cart: cartReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['user/fetchById/fulfilled'],
        ignoredPaths: ['user.user.metadata'],
      },
    }),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// Typed hooks
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

// 组件中使用
const UserProfile = () => {
  const { user, isLoading, error } = useAppSelector((state) => state.user);
  const dispatch = useAppDispatch();

  useEffect(() => {
    dispatch(fetchUserById('123'));
  }, [dispatch]);

  return (
    <div>
      {isLoading && <Skeleton />}
      {error && <Error message={error} />}
      {user && <Profile user={user} />}
    </div>
  );
};

3.2 数据请求与缓存

3.2.1 React Query / TanStack Query

【数据获取金标准】替代 Redux 处理服务器状态,是现代 React 应用的事实标准

// ✅ 基础用法
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

const UserProfile = ({ userId }) => {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => api.getUser(userId),
    staleTime: 5 * 60 * 1000, // 5分钟内不重新获取
    cacheTime: 10 * 60 * 1000, // 缓存保留10分钟
    enabled: !!userId, // 条件执行
  });

  if (isLoading) return <Skeleton />;
  if (error) return <Error error={error} />;

  return <Profile user={data} />;
};

// ✅ 创建 mutation(增删改)
const CreateUser = () => {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newUser) => api.createUser(newUser),

    onSuccess: (data) => {
      // 使 user 列表缓存失效,重新获取
      queryClient.invalidateQueries({ queryKey: ['users'] });

      // 或者直接更新缓存
      queryClient.setQueryData(['user', data.id], data);
    },

    onError: (error) => {
      toast.error(error.message);
    },
  });

  return (
    <button
      onClick={() => mutation.mutate({ name: 'John' })}
      disabled={mutation.isLoading}
    >
      {mutation.isLoading ? '创建中...' : '创建用户'}
    </button>
  );
};

// ✅ 高级配置
const useUsers = (params) => {
  return useQuery({
    queryKey: ['users', params],
    queryFn: () => api.getUsers(params),

    // 分页支持
    keepPreviousData: true, // 保持上一页数据直到新数据返回

    // 重试配置
    retry: 3,
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),

    // 轮询
    refetchInterval: 30000, // 每30秒轮询(仅在窗口聚焦时)
    refetchIntervalInBackground: false,
  });
};

// ✅ 并行查询
const Dashboard = ({ userId }) => {
  const userQuery = useQuery({ queryKey: ['user', userId], queryFn: () => api.getUser(userId) });
  const ordersQuery = useQuery({ queryKey: ['orders', userId], queryFn: () => api.getOrders(userId) });
  const notificationsQuery = useQuery({ queryKey: ['notifications'], queryFn: () => api.getNotifications() });

  // 或者使用 useQueries
  const results = useQueries({
    queries: [
      { queryKey: ['user', userId], queryFn: () => api.getUser(userId) },
      { queryKey: ['orders', userId], queryFn: () => api.getOrders(userId) },
    ],
  });
};
3.2.2 SWR —— Vercel 出品的轻量替代
// ✅ SWR 基础用法
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(res => res.json());

const UserProfile = ({ userId }) => {
  const { data, error, isLoading, mutate } = useSWR(
    userId ? `/api/user/${userId}` : null,
    fetcher,
    {
      revalidateOnFocus: true,  // 窗口聚焦时重新验证
      revalidateOnReconnect: true,
      dedupingInterval: 5000,   // 5秒内相同请求去重
    }
  );

  const handleUpdate = async () => {
    await mutate(
      api.updateUser(userId, newData), // 更新数据
      { revalidate: true }  // 重新验证
    );
  };

  return (
    <div>
      {isLoading && <Skeleton />}
      {error && <Error />}
      {data && <Profile user={data} />}
    </div>
  );
};

// ✅ 乐观更新
const UpdateName = () => {
  const { data, mutate } = useSWR('/api/user', fetcher);

  const handleSubmit = async (name) => {
    // 乐观更新:立即显示新名字
    const previousData = data;
    mutate({ ...data, name }, false);

    try {
      await api.updateName(name);
    } catch {
      // 失败时回滚
      mutate(previousData, false);
    }
  };

  return <Form onSubmit={handleSubmit} initialName={data?.name} />;
};
📊 React Query vs SWR 对比
特性 React Query SWR
Bundle Size ~13kb ~4kb
社区活跃度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
DevTools ✅ 官方提供 ❌ 第三方
缓存管理 强大且灵活 基础但够用
适用场景 中大型应用 小中型应用
TypeScript 支持 ✅ 优秀 ✅ 良好
学习曲线 略陡 平缓

3.3 状态划分策略

3.3.1 三层状态架构

【架构原则】根据状态的使用范围和更新频率选择合适的管理方案

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  【服务器状态】Server State                                  │
│  ├── 来源:API 请求、GraphQL                               │
│  ├── 管理:React Query / SWR / Apollo Client                │
│  ├── 特点:异步、缓存、自动同步、需要考虑数据一致性           │
│  └── 存储位置:Query Cache                                  │
│                                                             │
│  【全局状态】Global State                                    │
│  ├── 来源:用户信息、主题、语言设置、购物车                  │
│  ├── 管理:Zustand / Context + useReducer / Redux Toolkit   │
│  ├── 特点:同步、需要跨组件共享                              │
│  └── 存储位置:内存 / LocalStorage(持久化)                 │
│                                                             │
│  【本地状态】Local State                                     │
│  ├── 来源:表单输入、UI 状态(弹窗、展开/折叠)              │
│  ├── 管理:useState / useReducer                            │
│  ├── 特点:同步、不需要跨组件共享                            │
│  └── 存储位置:组件内部                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
3.3.2 状态管理方案决策树
需要管理状态时,按照以下顺序决策:

1. 【是服务器状态吗?】(用户数据、列表等来自 API)
   │
   ├─ YES → 使用 React Query / SWR
   │         ├─ 需要乐观更新?→ React Query
   │         ├─ 需要分页/无限滚动?→ React Query
   │         └─ 简单场景?→ SWR
   │
   └─ NO → 继续判断...

2. 【需要跨组件共享吗?】
   │
   ├─ YES → 是全局共享还是模块共享?
   │         ├─ 全局(主题/用户/语言)→ Context API / Zustand
   │         └─ 模块(单个功能模块)→ Zustand / useReducer
   │
   └─ NO → 使用 useState

3. 【状态逻辑复杂吗?】(多个相关状态、多种操作)
   │
   ├─ YES → useReducer / Zustand
   │
   └─ NO → useState
3.3.3 实践案例:电商应用状态架构
// 【服务器状态】产品数据 - React Query
export const useProducts = (categoryId) => useQuery({
  queryKey: ['products', categoryId],
  queryFn: () => api.getProducts(categoryId),
});

// 【全局状态】用户信息 - Context
const UserContext = createContext(null);

// 【全局状态】购物车 - Zustand
const useCartStore = create(persist(
  (set, get) => ({
    items: [],
    addItem: (product) => set(state => ({
      items: [...state.items, { ...product, quantity: 1 }]
    })),
    removeItem: (id) => set(state => ({
      items: state.items.filter(item => item.id !== id)
    })),
    total: () => get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
  }),
  { name: 'cart-storage' } // 持久化到 localStorage
));

// 【模块状态】订单表单 - useReducer
const orderReducer = (state, action) => {
  switch (action.type) {
    case 'SET_ADDRESS':
      return { ...state, shippingAddress: action.payload };
    case 'SET_PAYMENT':
      return { ...state, paymentMethod: action.payload };
    case 'APPLY_COUPON':
      return { ...state, coupon: action.payload, discount: calculateDiscount(action.payload) };
    case 'RESET':
      return initialOrderState;
    default:
      return state;
  }
};

// 【本地状态】UI 状态 - useState
const CheckoutPage = () => {
  // 服务器状态
  const { data: cartItems } = useProducts('cart');

  // 全局状态
  const { items, removeItem } = useCartStore();

  // 模块状态
  const [order, dispatch] = useReducer(orderReducer, initialOrderState);

  // 本地状态
  const [isModalOpen, setIsModalOpen] = useState(false);

  // ...
};

四、路由 & 权限进阶

4.1 路由进阶与代码分割

4.1.1 路由懒加载与代码分割

【性能优化】减少首屏加载时间,按需加载路由组件

// ✅ React.lazy + Suspense 实现路由懒加载
import { lazy, Suspense } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

// 懒加载路由组件
const HomePage = lazy(() => import('./pages/Home'));
const AboutPage = lazy(() => import('./pages/About'));
const ProductPage = lazy(() => import('./pages/Product'));
const DashboardPage = lazy(() => import('./pages/Dashboard'));
const NotFoundPage = lazy(() => import('./pages/NotFound'));

// 路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        index: true,
        element: <HomePage />,
      },
      {
        path: 'about',
        element: <AboutPage />,
      },
      {
        path: 'products/:productId',
        element: <ProductPage />,
      },
    ],
  },
  {
    path: '/dashboard',
    element: <PrivateRoute />,
    children: [
      {
        path: '',
        element: <DashboardPage />,
      },
    ],
  },
  {
    path: '*',
    element: <NotFoundPage />,
  },
]);

// App 入口
const App = () => (
  // Suspense 处理加载状态
  <Suspense fallback={<PageLoader />}>
    <RouterProvider router={router} />
  </Suspense>
);

// ✅ 使用 Vite 的预加载功能优化体验
const ProductPage = lazy(() =>
  import('./pages/Product').then(module => {
    // Vite 会预加载这个 chunk
    return module;
  })
);

// ✅ 基于路由的数据加载(loader)
import { createBrowserRouter, useLoaderData } from 'react-router-dom';

const ProductDetail = () => {
  const { product } = useLoaderData(); // 从 loader 获取数据

  return <ProductView product={product} />;
};

const productLoader = async ({ params }) => {
  const product = await api.getProduct(params.productId);
  return { product };
};

const router = createBrowserRouter([
  {
    path: 'products/:productId',
    element: <ProductDetail />,
    loader: productLoader, // 路由级别数据预加载
  },
]);
4.1.2 动态路由与嵌套路由
// ✅ 动态路由参数
const router = createBrowserRouter([
  {
    path: '/users/:userId',           // 动态段
    element: <UserLayout />,
    children: [
      {
        path: '',                      // /users/:userId
        element: <UserProfile />,
      },
      {
        path: 'posts',                 // /users/:userId/posts
        element: <UserPosts />,
      },
      {
        path: 'settings/*',            // 嵌套通配符
        element: <UserSettings />,
      },
    ],
  },
]);

// ✅ useParams 获取参数
const UserProfile = () => {
  const { userId } = useParams(); // { userId: '123' }

  return <div>User ID: {userId}</div>;
};

// ✅ 搜索参数
const SearchPage = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  const query = searchParams.get('q') || '';
  const page = parseInt(searchParams.get('page') || '1');

  const handleSearch = (newQuery) => {
    setSearchParams({ q: newQuery, page: '1' });
  };

  const handlePageChange = (newPage) => {
    setSearchParams({ q: query, page: String(newPage) });
  };

  return (
    <div>
      <SearchInput value={query} onChange={handleSearch} />
      <Results query={query} page={page} />
      <Pagination page={page} onChange={handlePageChange} />
    </div>
  );
};

// ✅ 编程式导航
const NavigateComponent = () => {
  const navigate = useNavigate();

  const goToProduct = (id) => {
    navigate(`/products/${id}`);
  };

  const goBack = () => {
    navigate(-1); // 浏览器历史的后退
  };

  const replaceToHome = () => {
    navigate('/', { replace: true }); // 替换当前历史记录
  };

  return (
    <div>
      <button onClick={() => goToProduct('123')}>查看产品</button>
      <button onClick={goBack}>返回</button>
      <button onClick={replaceToHome}>回首页(替换)</button>
    </div>
  );
};
4.1.3 路由守卫与权限控制

【安全关键】路由守卫必须配合后端权限验证,前端只是 UX 优化

// ✅ 路由守卫组件
const ProtectedRoute = ({ children, requiredRoles = [] }) => {
  const { isAuthenticated, user, isLoading } = useAuth();
  const location = useLocation();

  // 加载中状态
  if (isLoading) {
    return <FullPageSpinner />;
  }

  // 未登录:重定向到登录页
  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  // 角色验证
  if (requiredRoles.length > 0 && !requiredRoles.includes(user.role)) {
    return <Navigate to="/unauthorized" replace />;
  }

  return children;
};

// ✅ 带权限的路由配置
const router = createBrowserRouter([
  {
    path: '/admin',
    element: (
      <ProtectedRoute requiredRoles={['admin', 'super_admin']}>
        <AdminLayout />
      </ProtectedRoute>
    ),
    children: [
      { path: 'users', element: <UserManagement /> },
      { path: 'settings', element: <AdminSettings /> },
    ],
  },
  {
    path: '/profile',
    element: (
      <ProtectedRoute>
        <ProfilePage />
      </ProtectedRoute>
    ),
  },
]);

// ✅ 路由级别的数据加载(权限检查)
const adminLoader = async () => {
  const user = await getCurrentUser();

  if (!user || !hasPermission(user, 'admin:access')) {
    throw new Response('Unauthorized', { status: 401 });
  }

  return { adminData: await fetchAdminData() };
};

const router = createBrowserRouter([
  {
    path: '/admin',
    element: <AdminLayout />,
    loader: adminLoader, // 路由加载时检查权限
  },
]);

4.2 错误处理与边界

4.2.1 React 错误边界(Error Boundaries)

【错误恢复】错误边界是 React 16 引入的用于捕获子组件错误的机制

// ✅ 类组件实现错误边界
class ErrorBoundary extends React.Component<
  { children: React.ReactNode; fallback?: React.ReactNode },
  { hasError: boolean; error: Error | null }
> {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Error caught:', error, errorInfo);

    // 上报错误到监控服务
    logErrorToSentry(error, errorInfo);
  }

  resetError = () => {
    this.setState({ hasError: false, error: null });
  };

  render() {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback;
      }

      return (
        <div className="error-boundary">
          <h2>出现了一些问题</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={this.resetError}>重试</button>
          <a href="/">返回首页</a>
        </div>
      );
    }

    return this.props.children;
  }
}

// ✅ 函数组件实现(使用 react-error-boundary 库更方便)
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';

const fallbackRender = ({ error, resetErrorBoundary }) => (
  <div role="alert">
    <p>出现错误了:</p>
    <pre>{error.message}</pre>
    <button onClick={resetErrorBoundary}>重试</button>
  </div>
);

const App = () => (
  <ReactErrorBoundary fallbackRender={fallbackRender}>
    <MyComponent />
  </ReactErrorBoundary>
);
4.2.2 路由级错误处理
// ✅ 路由配置中的 errorElement
const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    errorElement: <GlobalErrorPage />, // 任何子路由错误都会显示
    children: [
      {
        path: 'products/:id',
        element: <ProductPage />,
        errorElement: <ProductErrorPage />, // 产品页专属错误
      },
    ],
  },
]);

// ✅ useRouteError 获取错误信息
import { useRouteError } from 'react-router-dom';

const GlobalErrorPage = () => {
  const error = useRouteError();

  return (
    <div className="error-page">
      <h1>{error.status || 'Oops!'}</h1>
      <p>{error.statusText || error.message}</p>
      <a href="/">返回首页</a>
    </div>
  );
};
⚠️ 错误边界无法捕获的情况
// ❌ 以下情况错误边界无法捕获:
// 1. 事件处理函数中的错误
const BadComponent = () => (
  <button onClick={() => { throw new Error('Oops!'); }}>
    Click me
  </button>
);

// 2. 异步代码中的错误(setTimeout, Promise)
const BadAsyncComponent = () => {
  useEffect(() => {
    setTimeout(() => {
      throw new Error('Oops!'); // 不会被捕获
    }, 1000);
  }, []);

  return <div>Async Error</div>;
};

// 3. 服务端渲染(SSR)错误
// 4. 错误边界组件本身的错误

五、TypeScript + React 工程化

5.1 TypeScript 核心概念

5.1.1 类型基础
// ✅ 基本类型
const name: string = 'John';
const age: number = 30;
const isActive: boolean = true;
const hobbies: string[] = ['reading', 'coding'];
const scores: Array<number> = [90, 85, 88];

// ✅ 对象类型
interface User {
  id: string;
  name: string;
  email: string;
  age?: number;              // 可选属性
  readonly createdAt: Date;  // 只读属性
  role: 'admin' | 'user' | 'guest';  // 联合类型
}

// ✅ 函数类型
type Handler = (event: MouseEvent) => void;
type AsyncHandler = (data: string) => Promise<void>;

// ✅ 泛型
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

interface ListResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
}

// 使用泛型
const response: ApiResponse<User> = {
  data: { id: '1', name: 'John', email: 'john@example.com', createdAt: new Date(), role: 'user' },
  status: 200,
  message: 'Success',
};

// ✅ 泛型约束
interface HasId {
  id: string;
}

function findById<T extends HasId>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}
5.1.2 接口 vs 类型别名
// ✅ interface:适合声明对象结构,支持声明合并
interface User {
  id: string;
  name: string;
}

interface User {
  email: string; // 合并到上面的 User
}

// 继承
interface Admin extends User {
  permissions: string[];
}

// ✅ type alias:适合复杂类型联合、交叉
type Status = 'pending' | 'loading' | 'success' | 'error';
type Result<T, E = Error> = { data: T; error: null } | { data: null; error: E };

// 交叉类型
type ExtendedUser = User & { extra: string };

// ✅ 最佳实践:
// - 用 interface 声明对象结构(可扩展性好)
// - 用 type alias 处理联合类型、元组、工具类型
5.1.3 实用工具类型
// ✅ 常用工具类型
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

// Partial:所有属性变为可选
type PartialUser = Partial<User>;
// { id?: string; name?: string; email?: string; age?: number }

// Required:所有属性变为必需
type RequiredUser = Required<User>;

// Pick:选取部分属性
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: string; name: string }

// Omit:排除部分属性
type UserWithoutEmail = Omit<User, 'email'>;
// { id: string; name: string; age: number }

// Record:键值对
type UserMap = Record<string, User>;

// ReturnType:提取函数返回值
const getUser = () => ({ id: '1', name: 'John' });
type UserResult = ReturnType<typeof getUser>;

// Parameters:提取函数参数
function createUser(name: string, age: number) { /* ... */ }
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number]

5.2 React + TypeScript 最佳实践

5.2.1 组件 Props 类型定义
// ✅ 函数组件 Props 类型
interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
  type?: 'button' | 'submit' | 'reset';
  className?: string;
}

const Button: React.FC<ButtonProps> = ({
  children,
  variant = 'primary',
  size = 'medium',
  disabled = false,
  onClick,
  type = 'button',
  className,
}) => {
  return (
    <button
      type={type}
      className={`btn btn--${variant} btn--${size} ${className || ''}`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
};

// ✅ 推荐写法:使用 Props 类型参数(React 18+)
const Button = <T extends React.ReactNode>({
  children,
  variant = 'primary',
}: {
  children: T;
  variant?: 'primary' | 'secondary';
}) => {
  return <button className={`btn btn--${variant}`}>{children}</button>;
};

// ✅ 使用 as Props 的组件
interface FormProps {
  children: React.ReactNode;
  onSubmit: (data: FormData) => void;
}

const Form: React.FC<FormProps> = ({ children, onSubmit }) => {
  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    onSubmit(Object.fromEntries(formData));
  };

  return <form onSubmit={handleSubmit}>{children}</form>;
};

Form.Input = ({ name, ...props }) => (
  <input name={name} {...props} />
);
5.2.2 Hooks 类型定义
// ✅ useState 类型推断
const [count, setCount] = useState(0); // number
const [user, setUser] = useState<User | null>(null); // 显式指定
const [loading, setLoading] = useState(false); // boolean
const [items, setItems] = useState<Item[]>([]); // 数组

// ✅ useRef 类型
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);

// 使用
inputRef.current?.focus();
inputRef.current?.value;

// ✅ useEffect 返回类型
useEffect(() => {
  const controller = new AbortController();
  return () => controller.abort(); // cleanup 函数类型自动推断
}, []);

// ✅ useReducer 泛型
interface State {
  count: number;
  loading: boolean;
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'set'; payload: number }
  | { type: 'setLoading'; payload: boolean };

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'set':
      return { ...state, count: action.payload };
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(reducer, { count: 0, loading: false });

// ✅ 自定义 Hooks 类型
function useFetch<T>(url: string): {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => void;
} {
  // ...
}
5.2.3 事件处理器类型
// ✅ 常用事件类型
const EventExamples = () => {
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    console.log(e.currentTarget.name);
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log('Submitted');
  };

  const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
    console.log('Focused:', e.target.name);
  };

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  };

  const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
    console.log('ScrollTop:', e.currentTarget.scrollTop);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        onChange={handleChange}
        onFocus={handleFocus}
        onKeyDown={handleKeyDown}
      />
      <button name="submit" onClick={handleClick}>Submit</button>
    </form>
  );
};

// ✅ 拖拽事件
const Draggable = () => {
  const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
    e.dataTransfer.setData('text/plain', 'dragged-item');
  };

  const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    const data = e.dataTransfer.getData('text/plain');
    console.log('Dropped:', data);
  };

  return (
    <div
      draggable
      onDragStart={handleDragStart}
      onDrop={handleDrop}
    >
      Drag me
    </div>
  );
};

5.3 类型工程化

5.3.1 全局类型声明
// ✅ types/global.d.ts
declare global {
  interface Window {
    __INITIAL_DATA__: any;
    gtag: (...args: any[]) => void;
  }

  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production' | 'test';
      REACT_APP_API_URL: string;
      REACT_APP_VERSION: string;
    }
  }
}

// ✅ 模块声明
declare module '*.module.css' {
  const classes: { [key: string]: string };
  export default classes;
}

declare module '*.svg' {
  const content: React.FC<React.SVGProps<SVGElement>>;
  export default content;
}

declare module '*.mp4' {
  const src: string;
  export default src;
}

// ✅ 全局工具类型
type Nullable<T> = T | null;
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type AsyncReturnType<T extends (...args: any) => Promise<any>> =
  T extends (...args: any) => Promise<infer R> ? R : any;

// ✅ API 响应类型
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
  hasMore: boolean;
}
5.3.2 类型守卫与类型断言
// ✅ 类型守卫
interface Cat {
  meow(): void;
}

interface Dog {
  bark(): void;
}

function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).meow !== undefined;
}

function speak(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow();
  } else {
    animal.bark();
  }
}

// ✅ in 操作符类型守卫
function isClickable(element: HTMLElement): element is HTMLElement {
  return 'onclick' in element || element.getAttribute('role') === 'button';
}

// ✅ typeof 类型守卫
type StringOrNumber = string | number;
function isString(value: StringOrNumber): value is string {
  return typeof value === 'string';
}

// ✅ 类型断言
// as 断言(推荐)
const input = document.getElementById('myInput') as HTMLInputElement;
input.value = 'hello';

// 非空断言(慎用)
const name = user!.name; // 假设 user 一定存在

// 类型映射
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Optional<T> = {
  [P in keyof T]?: T[P];
};
5.3.3 类型复用与抽象
// ✅ 创建可复用的类型工具
// types/api.ts
export const createApiSlice = <T>(endpoint: string) => ({
  queries: {
    list: () => useQuery<T[]>(`${endpoint}`),
    byId: (id: string) => useQuery<T>(`${endpoint}/${id}`),
  },
  mutations: {
    create: () => useMutation<T, T>(`${endpoint}`),
    update: () => useMutation<T, { id: string; data: Partial<T> }>(`${endpoint}`),
    delete: () => useMutation<void, string>(`${endpoint}`),
  },
});

// 使用
const userApi = createApiSlice<User>('/api/users');
const { data: users } = userApi.queries.list();
const { data: user } = userApi.queries.byId('123');

六、性能优化

6.1 渲染优化策略

6.1.1 React.memo 最佳实践

【避免重渲染】React.memo 包裹组件,防止 props 未变化时的重渲染

// ✅ 基础用法
const MemoizedComponent = React.memo(function MyComponent({ name, onClick }) {
  console.log('Component rendered'); // 观察渲染次数

  return (
    <div onClick={onClick}>
      {name}
    </div>
  );
});

// ✅ 自定义比较函数(性能关键)
const OptimizedListItem = React.memo(
  ({ id, name, price }) => (
    <li key={id}>
      {name} - ${price}
    </li>
  ),
  // 自定义比较:仅在 id 和 price 变化时重渲染
  (prevProps, nextProps) => {
    return (
      prevProps.id === nextProps.id &&
      prevProps.price === nextProps.price
    );
  }
);

// ❌ 常见错误:比较函数返回 false(永远重渲染)
const BadMemo = React.memo(
  ({ data }) => <div>{data.map(item => item.name)}</div>,
  () => false // 💀 总是返回 false = 永远重渲染
);

// ✅ 正确的对象比较
const GoodParent = () => {
  const [count, setCount] = useState(0);

  // ❌ 每次渲染都创建新对象
  const config = { padding: 20 };

  // ✅ 使用 useMemo 稳定对象引用
  const config = useMemo(() => ({ padding: 20 }), []);

  // ✅ 或者传递原始值
  const padding = 20;

  return (
    <MemoizedChild
      config={config}
      padding={padding}
      onClick={() => setCount(c => c + 1)}
    />
  );
};

const MemoizedChild = React.memo(({ config, padding, onClick }) => {
  console.log('MemoizedChild rendered');

  return (
    <div style={{ padding }} onClick={onClick}>
      <DeepTree data={config} />
    </div>
  );
});
6.1.2 常见重渲染原因与解决方案
// ✅ 场景 1:内联函数作为 props
const BadParent = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <Child onClick={() => console.log('clicked')} /> {/* 新函数每次 */}
      <button onClick={() => setCount(c => c + 1)}>Re-render</button>
    </>
  );
};

const GoodParent = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return (
    <>
      <Child onClick={handleClick} />
      <button onClick={() => setCount(c => c + 1)}>Re-render</button>
    </>
  );
};

// ✅ 场景 2:内联对象作为 props
const BadParent2 = () => {
  return <Child style={{ color: 'red' }} />; // 新对象每次
};

const GoodParent2 = () => {
  const style = useMemo(() => ({ color: 'red' }), []);
  return <Child style={style} />;
};

// ✅ 场景 3:数组作为 props
const BadParent3 = () => {
  return <List data={[1, 2, 3]} />; // 新数组每次
};

const GoodParent3 = () => {
  const data = useMemo(() => [1, 2, 3], []);
  return <List data={data} />;
};
6.1.3 状态位置优化

【状态提升的艺术】把状态放在需要它的地方,而非更高层级

// ❌ 过度提升:导致不必要的重渲染
const过度提升 = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Header /> {/* 不需要 count 但会重渲染 */}
      <Sidebar /> {/* 不需要 count 但会重渲染 */}
      <Main>
        <Counter />
      </Main>
    </div>
  );
};

// ✅ 优化:状态放在需要它的组件内部
const 优化后 = () => (
  <div>
    <Header />
    <Sidebar />
    <Main>
      <Counter /> {/* count 状态在这里 */}
    </Main>
  </div>
);

// ✅ 共享状态但独立订阅
const SharedCounter = ({ initialCount = 0 }) => {
  const [count, setCount] = useState(0);

  return <CounterContext.Provider value={{ count, setCount }}>
    {children}
  </CounterContext.Provider>;
};

const CounterDisplay = () => {
  const { count } = useContext(CounterContext);
  return <div>{count}</div>; // 仅 count 变化时重渲染
};

const CounterButton = () => {
  const { setCount } = useContext(CounterContext);
  return <button onClick={() => setCount(c => c + 1)}>+</button>;
};

6.2 资源优化

6.2.1 图片优化
// ✅ 响应式图片
const ResponsiveImage = ({ src, alt }) => (
  <picture>
    <source
      media="(min-width: 1024px)"
      srcSet={`${src}-1024.jpg`}
    />
    <source
      media="(min-width: 768px)"
      srcSet={`${src}-768.jpg`}
    />
    <img
      src={`${src}-480.jpg`}
      alt={alt}
      loading="lazy" // 懒加载
      decoding="async" // 异步解码
    />
  </picture>
);

// ✅ 使用 modern 图片格式
const ModernImage = ({ src, alt }) => (
  <picture>
    <source srcSet={`${src}.avif`} type="image/avif" />
    <source srcSet={`${src}.webp`} type="image/webp" />
    <img src={`${src}.jpg`} alt={alt} />
  </picture>
);

// ✅ 占位符与骨架屏
const ImageWithSkeleton = ({ src, alt }) => {
  const [loaded, setLoaded] = useState(false);

  return (
    <div className={loaded ? '' : 'skeleton'}>
      {!loaded && <div className="skeleton-placeholder" />}
      <img
        src={src}
        alt={alt}
        onLoad={() => setLoaded(true)}
        style={{ opacity: loaded ? 1 : 0 }}
      />
    </div>
  );
};
6.2.2 虚拟列表(长列表优化)

【性能关键】对于 100+ 条数据的列表,必须使用虚拟滚动

// ✅ react-window 实现虚拟列表
import { FixedSizeList as List } from 'react-window';
import { VariableSizeList } from 'react-window';

const VirtualList = ({ items }) => {
  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <ListItem item={items[index]} />
        </div>
      )}
    </List>
  );
};

// ✅ 可变高度列表(需要测量)
const VariableHeightList = ({ items }) => {
  const getItemSize = (index) => {
    // 根据内容计算高度
    return items[index].hasImage ? 100 : 50;
  };

  return (
    <VariableSizeList
      height={400}
      itemCount={items.length}
      itemSize={getItemSize}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <VariableHeightItem item={items[index]} />
        </div>
      )}
    </VariableSizeList>
  );
};

// ✅ react-virtuoso(更现代,支持分组)
import { Virtuoso } from 'react-virtuoso';

const AutoSizingList = ({ items }) => (
  <Virtuoso
    style={{ height: 400 }}
    totalCount={items.length}
    itemContent={(index) => <Item data={items[index]} />}
  />
);
6.2.3 代码分割策略
// ✅ 路由级别分割
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

// ✅ 组件级别分割(按需加载)
const ChartComponent = lazy(() => import('./components/Chart'));
const HeavyEditor = lazy(() => import('./components/HeavyEditor'));

// ✅ 条件分割(仅在需要时加载)
const ConditionalFeature = ({ showFeature }) => {
  const Feature = useMemo(() =>
    showFeature ? lazy(() => import('./Feature')) : null,
    [showFeature]
  );

  return Feature ? (
    <Suspense fallback={<Spinner />}>
      <Feature />
    </Suspense>
  ) : null;
};

// ✅ Preload(提前加载)
const ProductPage = lazy(() => import('./pages/Product'));

// 在用户可能访问之前预加载
const preloadProductPage = () => {
  ProductPage(); // 开始加载,但不阻塞当前渲染
};

// hover 时预加载
const ProductLink = ({ id }) => (
  <Link
    to={`/products/${id}`}
    onMouseEnter={() => {
      import('./pages/Product'); // 预加载
    }}
  >
    View Product
  </Link>
);

6.3 构建优化

6.3.1 Tree Shaking 与代码压缩
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    target: 'es2015', // 兼容到 ES2015
    minify: 'terser', // 使用 terser 压缩
    terserOptions: {
      compress: {
        drop_console: true, // 生产环境移除 console
        drop_debugger: true,
      },
    },
    rollupOptions: {
      output: {
        // 手动分包
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router-dom'],
          ui: ['antd', '@ant-design/icons'],
        },
      },
    },
    // 启用 gzip 压缩
    reportCompressedSize: true,
  },
});
6.3.2 依赖分析
# 分析 bundle 组成
npm install rollup-plugin-visualizer -D

# vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';

plugins: [
  react(),
  visualizer({
    filename: 'bundle-stats.html', // 生成可视化报告
    open: true,
  }),
];

# 运行构建后打开 bundle-stats.html 查看组成
npm run build && open bundle-stats.html

七、样式 & 工程化进阶

7.1 样式方案深度对比

7.1.1 CSS Modules vs Styled Components vs Tailwind CSS

【选型决策】根据项目特点和团队选择合适的样式方案

维度 CSS Modules Styled Components Tailwind CSS
Bundle Size ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
运行时开销 CSS-in-JS 运行时
学习曲线 中(大量工具类)
IDE 支持 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
样式复用 手动 import 继承/组合 @apply 指令
动态样式 ❌ 需配合 CSS 变量 ✅ 原生支持 ⚠️ 通过 class 切换
适用场景 中大型项目 需要主题切换 快速原型/小团队
7.1.2 CSS Modules 实战
/* Button.module.css */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 14px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.button--primary {
  background: var(--color-primary);
  color: white;
}

.button--secondary {
  background: transparent;
  border: 1px solid var(--color-border);
}

.button--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.icon {
  margin-right: 8px;
}
// Button.tsx
import styles from './Button.module.css';

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
}

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  disabled = false,
  children,
  onClick,
}) => {
  return (
    <button
      className={`${styles.button} ${styles[`button--${variant}`]} ${disabled ? styles['button--disabled'] : ''}`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
};
7.1.3 Tailwind CSS 实战
// ✅ Tailwind 配置
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {
      colors: {
        primary: '#1890ff',
        secondary: '#52c41a',
      },
      spacing: {
        18: '4.5rem',
      },
    },
  },
  plugins: [],
};

// ✅ 使用
const TailwindButton = ({ children, variant = 'primary', onClick }) => (
  <button
    onClick={onClick}
    className={`
      px-4 py-2 rounded-md font-medium transition-colors
      ${variant === 'primary'
        ? 'bg-blue-500 text-white hover:bg-blue-600'
        : 'bg-gray-200 text-gray-800 hover:bg-gray-300'}
    `}
  >
    {children}
  </button>
);

// ✅ @apply 复用样式
.btn-base {
  @apply px-4 py-2 rounded-md font-medium transition-colors;
}

.btn-primary {
  @apply btn-base bg-blue-500 text-white hover:bg-blue-600;
}

7.2 测试基础

7.2.1 Jest + React Testing Library 基础

【质量保障】测试不是为了覆盖率,而是为了信心

// ✅ 组件测试
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';

describe('LoginForm', () => {
  const onSubmit = vi.fn();

  beforeEach(() => {
    onSubmit.mockClear();
  });

  it('should render form fields', () => {
    render(<LoginForm onSubmit={onSubmit} />);

    expect(screen.getByLabelText(/username/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
  });

  it('should show validation errors on empty submit', async () => {
    render(<LoginForm onSubmit={onSubmit} />);

    await userEvent.click(screen.getByRole('button', { name: /login/i }));

    expect(await screen.findByText(/username is required/i)).toBeInTheDocument();
    expect(onSubmit).not.toHaveBeenCalled();
  });

  it('should submit with valid data', async () => {
    render(<LoginForm onSubmit={onSubmit} />);

    await userEvent.type(screen.getByLabelText(/username/i), 'john');
    await userEvent.type(screen.getByLabelText(/password/i), 'password123');
    await userEvent.click(screen.getByRole('button', { name: /login/i }));

    expect(onSubmit).toHaveBeenCalledWith({
      username: 'john',
      password: 'password123',
    });
  });

  it('should handle password visibility toggle', () => {
    render(<LoginForm onSubmit={onSubmit} />);

    const passwordInput = screen.getByLabelText(/password/i);
    expect(passwordInput).toHaveAttribute('type', 'password');

    fireEvent.click(screen.getByRole('button', { name: /show password/i }));

    expect(screen.getByLabelText(/password/i)).toHaveAttribute('type', 'text');
  });
});

// ✅ Hooks 测试
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('should increment', () => {
    const { result } = renderHook(() => useCounter());

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });

  it('should decrement', () => {
    const { result } = renderHook(() => useCounter());

    act(() => {
      result.current.decrement();
    });

    expect(result.current.count).toBe(-1);
  });

  it('should reset to custom value', () => {
    const { result } = renderHook(() => useCounter(10));

    act(() => {
      result.current.reset();
    });

    expect(result.current.count).toBe(10);
  });
});
7.2.2 测试最佳实践
// ✅ 测试应该像用户一样使用组件
// ❌ 不要
const badTest = () => {
  const instance = new MyComponent();
  instance.setState({ name: 'John' });
  expect(instance.state.name).toBe('John');
};

// ✅ 正确
const goodTest = () => {
  render(<MyComponent />);
  fireEvent.change(screen.getByRole('textbox'), { target: { value: 'John' } });
  expect(screen.getByText('John')).toBeInTheDocument();
};

// ✅ 优先测试行为,而非实现细节
// ❌ 不要测试 state
// ✅ 测试渲染结果

// ✅ 使用 userEvent 而非 fireEvent(更接近真实用户交互)
// ❌ fireEvent.click(button);
// ✅ userEvent.click(button);

八、服务端渲染入门

8.1 Next.js 核心概念

8.1.1 Next.js 渲染模式

【架构决策】根据数据特性和用户体验选择合适的渲染策略

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  SSR (Server-Side Rendering)                                │
│  ├── 每次请求:服务器 → HTML → 浏览器                        │
│  ├── 适用:频繁变化、需要 SEO 的页面                         │
│  └── 示例:新闻详情、社交媒体帖子                            │
│                                                             │
│  SSG (Static Site Generation)                                │
│  ├── 构建时:服务器 → HTML → 静态文件                        │
│  ├── 适用:不频繁变化的内容                                 │
│  └── 示例:文档、博客文章、产品列表                          │
│                                                             │
│  ISR (Incremental Static Regeneration)                      │
│  ├── 构建 + 按需重新生成                                    │
│  ├── 适用:大量页面的中频更新                               │
│  └── 示例:电商产品页、用户资料                              │
│                                                             │
│  CSR (Client-Side Rendering)                                │
│  ├── 浏览器 → JavaScript → 内容                             │
│  ├── 适用:高度个性化、需登录的内容                         │
│  └── 示例:仪表盘、个性化推荐                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
8.1.2 Next.js 13+ App Router 基础
// app/page.tsx - 首页(SSG,默认)
export default function HomePage() {
  return <h1>Welcome to Next.js!</h1>;
}

// app/about/page.tsx - 关于页(SSG)
export default function AboutPage() {
  return <h1>About Us</h1>;
}

// app/products/[id]/page.tsx - 动态路由(SSR)
// 生成:/products/1, /products/2, ...
export default async function ProductPage({
  params,
}: {
  params: { id: string };
}) {
  // 服务端直接获取数据
  const product = await getProduct(params.id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

// app/blog/[slug]/page.tsx - 博客文章(SSG + ISR)
export const revalidate = 3600; // 每小时重新验证

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getBlogPost(params.slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}

// 静态生成所有博客文章(构建时)
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({ slug: post.slug }));
}

8.2 SSR/SSG 数据获取

8.2.1 服务端数据获取模式
// ✅ 服务端组件直接 async/await
async function getUser(id: string) {
  const res = await fetch(`https://api.example.com/users/${id}`, {
    // Next.js 默认缓存请求结果
    // 需要动态数据时使用
    cache: 'no-store', // 不缓存
    // cache: 'force-cache' // 强制缓存(默认)
    // next: { revalidate: 60 } // ISR 60秒后重新验证
  });

  if (!res.ok) throw new Error('Failed to fetch data');
  return res.json();
}

// app/users/[id]/page.tsx
export default async function UserProfile({
  params,
}: {
  params: { id: string };
}) {
  // 并行获取多个数据源
  const [user, posts] = await Promise.all([
    getUser(params.id),
    getUserPosts(params.id),
  ]);

  return (
    <div>
      <UserHeader user={user} />
      <UserPosts posts={posts} />
    </div>
  );
}
8.2.2 客户端数据获取(CSR)
// ✅ 使用 useEffect(简单场景)
'use client';

import { useState, useEffect } from 'react';

export function UserData({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchUser = async () => {
      setLoading(true);
      const data = await getUser(userId);
      setUser(data);
      setLoading(false);
    };

    fetchUser();
  }, [userId]);

  if (loading) return <Skeleton />;
  return <UserProfile user={user} />;
}

// ✅ 推荐:使用 SWR(客户端缓存)
'use client';

import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

export function UserData({ userId }: { userId: string }) {
  const { data: user, error, isLoading } = useSWR(
    `/api/users/${userId}`,
    fetcher,
    {
      revalidateOnFocus: false,
    }
  );

  if (isLoading) return <Skeleton />;
  if (error) return <Error />;
  return <UserProfile user={user} />;
}
8.2.3 Hydration 问题与解决
// ✅ 避免 Hydration 不匹配
// ❌ 错误:在服务端和客户端渲染不同内容
const BadComponent = () => {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  // 服务端:mounted = false
  // 客户端:mounted = false(首次),然后 true(mount后)
  // 导致 hydration mismatch
  return <div>{mounted ? 'Client' : 'Server'}</div>;
};

// ✅ 正确:使用 useEffect 只在客户端执行
const GoodComponent = () => {
  const [value, setValue] = useState('');

  useEffect(() => {
    // 只在客户端执行
    setValue(localStorage.getItem('key') || '');
  }, []);

  return <div>{value}</div>;
};

// ✅ 使用 suppressHydrationWarning(必要时)
const DateComponent = ({ date }) => {
  return (
    <time dateTime={date} suppressHydrationWarning>
      {new Date(date).toLocaleDateString()}
    </time>
  );
};

// ✅ 使用动态导入禁用 SSR
import dynamic from 'next/dynamic';

const NoSSRComponent = dynamic(
  () => import('./HeavyComponent'),
  { ssr: false }
);

export function Page() {
  return (
    <div>
      <h1>Static Content</h1>
      <NoSSRComponent /> {/* 这个组件不会在服务端渲染 */}
    </div>
  );
}

附录:进阶学习路线图

📚 官方资源

🛠️ 推荐工具链(进阶)

  • 状态管理:Zustand / Redux Toolkit / Jotai
  • 数据获取:TanStack Query / SWR
  • 样式:Tailwind CSS / CSS Modules
  • 构建:Vite / Turbopack
  • 类型检查:TypeScript 5+
  • 测试:Vitest + React Testing Library

📖 进阶方向(完成进阶层后)

  1. React 源码探索:Fiber、Reconciler、Scheduler 源码分析
  2. 性能调优:Profiling、Chrome DevTools Performance 面板
  3. 微前端架构:qiankun / Module Federation
  4. Server Components:Next.js App Router 深度应用
  5. React Native:跨平台移动开发
  6. 状态机:XState 状态管理
  7. 可视化:D3.js / Three.js / React-Three-Fiber
  8. 动画:Framer Motion / GSAP

🎯 实战项目建议(进阶)

Level 5:团队协作中后台(2-4周)

  • 目标:使用 TypeScript + Zustand + React Query + Tailwind CSS
  • 要点:复杂表单、权限管理、数据可视化、单元测试

Level 6:SSR 应用(2-3周)

  • 目标:掌握 Next.js App Router + 数据获取策略
  • 要点:SSR/SSG/ISR 选型、缓存策略、Hydration

Level 7:性能优化专项(1-2周)

  • 目标:实际优化一个慢应用, Lighthouse 分数 60 → 90+
  • 要点:Code Splitting、虚拟列表、缓存策略、Bundle 优化

总结

本文档从资深 React 架构师的视角,系统地梳理了 React 进阶开发的核心知识体系:

✅ 五大核心突破

  1. 底层原理

    • Fiber 架构实现异步可中断渲染
    • Diff 算法三假设实现 O(n) 复杂度
    • Hooks 链表设计实现状态管理
    • 合成事件系统的跨平台抽象
  2. 性能优化

    • React.memo + useCallback 防止无效渲染
    • 虚拟列表处理长列表
    • Code Splitting + Lazy Loading
    • 构建优化(Tree Shaking、分包)
  3. 状态管理

    • Zustand/Jotai/Redux Toolkit 选型
    • React Query/SWR 处理服务器状态
    • 三层状态架构(服务器/全局/本地)
  4. TypeScript 集成

    • Props/Hooks 类型定义
    • 类型工程化
    • 类型守卫与工具类型
  5. 服务端渲染

    • Next.js App Router
    • SSR/SSG/ISR 渲染策略
    • Hydration 问题解决

🎯 核心理念

  • 原理驱动:理解底层才能写出高效代码
  • 测量先行:用 Profiler 找瓶颈,而非猜测
  • 渐进增强:从简单方案开始,按需升级
  • 工程化保障:TypeScript + ESLint + 测试 = 可靠代码

📈 成长路径

Level 1-2: 熟练使用 API,完成基础项目
     ↓
Level 3-4: 理解原理,能解决复杂问题
     ↓
Level 5-6: 架构设计,性能优化
     ↓
Level 7+:  源码贡献,框架设计

下一步行动建议

  1. 完成进阶层文档学习,重点关注第一部分的底层原理
  2. 完成 Level 5 的团队协作项目(TypeScript 全家桶)
  3. 学习 Next.js,完成 Level 6 的 SSR 项目
  4. 尝试性能优化实战,用 Profiler 验证效果

版本:v1.0
最后更新:2026-05-10
前置要求:React 基础层文档(已完成)
适用版本:React 18+ / Next.js 14+ / TypeScript 5+
作者视角:基于 5 年+ React 大型项目实战 + 源码分析经验整理

更多推荐