什么是懒初始化(lazy initialization)?
React中的懒初始化是一种在状态(state)初始化过程中,当初始化任务成本高或耗时长时非常有用的技术。
为什么需要懒初始化
让我们看看下面的代码示例。以下代码的问题是,每次React组件重新渲染时,传递给useState的初始值函数都会被调用。
const veryHeavyWork = () => {
  console.log('very heavy work called');
  // 访问localStorage、复杂计算、API调用等
  // 耗时的操作...
  return { data: 'expensive data' };
};
export const ProblemComponent = () => {
  // 问题:每次重新渲染时都会调用veryHeavyWork
  const [expensiveState, setExpensiveState] = useState(veryHeavyWork());
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        增加(触发重新渲染)
      </button>
    </div>
  );
};在上面的代码中,每次点击"增加"按钮时都会调用veryHeavyWork函数。然而,由于expensiveState已经初始化并被react-reconciler记忆化,这个函数的返回值会被忽略,导致性能浪费。

应用懒初始化
在这种情况下,使用懒初始化可以防止在重新渲染期间再次执行昂贵的函数,如果它们已经被设置为初始值。方法很简单:不是传递函数执行的结果,而是传递一个返回函数的函数。
export const OptimizedComponent = () => {
  // 解决方案:传递一个返回函数的函数
  const [expensiveState, setExpensiveState] = useState(() => veryHeavyWork());
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        增加(已优化)
      </button>
    </div>
  );
};
换句话说,通过将执行时机委托给React而不是由开发者直接决定,可以解决这个问题。这种技术之所以被称为"懒初始化",是因为它延迟执行直到必要时才进行。
JavaScript的评估时机
初始化
在将参数传递给useState之前,veryHeavyWork函数在JavaScript代码评估阶段就被执行。由于重新渲染意味着重新执行组件函数,所以这个函数每次都会被调用。函数执行后,返回值被传递给useState,但这个值被忽略,因为react-reconciler使用存储在hook的memoizedState中的现有值。
const [expensiveState, setexpensiveState] = useState(veryHeavyWork());重新渲染
然而,如果像下面这样编写,一个返回函数的函数在执行veryHeavyWork函数之前作为值传递给useState。因此,它不会在JavaScript评估代码的时候执行。
const [expensiveState, setexpensiveState] = useState(() => veryHeavyWork());React如何做出这个决定
初始化
React是如何做出这种判断的?让我们分析React代码!
当提供初始值时,mountState被执行。我们可以看到它返回memoizedState和dispatch作为返回值。这些就是我们使用的const [state, setState] = useState()。
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountStateImpl(initialState);
  // 省略...
  queue.dispatch = dispatch;
  return [hook.memoizedState, dispatch];
}``
现在让我们看看实现,mountStateImpl。如果初始值作为函数传入,我们可以看到它将执行该函数的结果存储在initialState中,然后再将该值存储在memoizedState中。
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    const initialStateInitializer = initialState;
    initialState = initialStateInitializer();
    // 省略...
  }
  hook.memoizedState = hook.baseState = initialState;
  // 省略..
  return hook;
}重新渲染
现在让我们看看重新渲染期间运行的代码。在重新渲染期间,rerenderState → rerenderReducer按顺序运行。从代码中可以看出,它只是检索并返回先前存储的memoizedState。
function rerenderReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  let newState = hook.memoizedState;
  // 如果在上一个渲染阶段调用了dispatch则执行
  if (lastRenderPhaseUpdate !== null) {
    // 省略
  }
  return [newState, dispatch];
}什么时候应该使用它?
如上所述,当生成初始值需要很长时间计算时,使用它会很好。例如,当访问localStorage或sessionStorage,或使用数组方法时。
- 
访问localStorage/sessionStorage javascriptconst [user, setUser] = useState(() => { const savedUser = localStorage.getItem('user'); return savedUser ? JSON.parse(savedUser) : null; });
- 
生成复杂的初始数据 javascriptconst [chartData, setChartData] = useState(() => { return generateComplexChartData(rawData); });
- 
处理大型数组或对象 javascriptconst [processedItems, setProcessedItems] = useState(() => { return largeDataSet.map(item => ({ ...item, processed: true, timestamp: Date.now() })); });
与使用useEffect初始化的区别
useState的懒初始化与使用useEffect在组件渲染时初始化状态之间有几个区别。
初始化时机
- 懒初始化: 在状态变量声明时直接设置值。它同步工作,并且在组件首次渲染之前设置值,因此可以在组件挂载和首次渲染发生之前使用该值。
- useEffect: 由于它在组件渲染后初始化,你可能会注意到屏幕上的值变化。
使用场景
- 懒初始化: 当你需要在组件挂载前使用值时
- useEffect:对于异步任务或发生副作用时

 翻译。如有任何翻译错误,请告知我们!
翻译。如有任何翻译错误,请告知我们!