React.js - 离开 vue 的我,和从前的 react 伙伴往 useState Lazy Initializa

在有需求时发现的新知识,总是特别难忘!

在 react 专案效能调整上,发现了最基础的玩意。

 

 

前言

因爲工作使用 React 开发,对于许久没碰的我来说,难免错过了许多好料。

这篇的原由是有一次「质疑」现有专案的写法:

const [list, setList] = useState(() => getList());
阅读他人的代码,先质疑,再理解。这也是一种成长的方式。

 

仔细查了,才发现原来这叫做「useState Lazy Initialization」。

回到最基础的文件上,可以找到这篇:Avoiding recreating the initial state 。

它就是在讲述使用useState,传入function可以使状态仅在初始时 render。

 

如果 function需要花费较高的运算时间,甚至引起画面卡顿,那么这是一个有效提升效能的做法。

中文应该叫做:惰性初始 state

 

这篇记录学习内容,阅读后你应该会知道 useState lazy initialization:

  1. 如何正确起手式。
  2. 概念与实践方式。
  3. 有无使用的差异。
     

 

 

一、初始化方式的两种模式

1. 直接传递初始值

const [todos, setTodos] = useState(createInitialTodos());
  • 初始值会在 组件每次渲染时 计算。
  • 如果createInitialTodos()运算较重,将会影响效能。

 

2. 传递函数进行 lazy initialization

const [todos, setTodos] = useState(() => createInitialTodos());
// or 
const [todos, setTodos] = useState(createInitialTodos);
  • React 只会在第一次渲染时执行该函数,并将结果作为初始状态。
  • 避免在每次渲染时执行昂贵的计算。

 

3. 建立昂贵计算

尝试将createInitialTodos弄得複杂一点,你会发现开始延迟了。

/**
 * @param {number} depth
 * @param {number} size
 */
function createInitialTodos(depth = 3, size = 100) {
  if (depth === 0) {
    return Array(size)
      .fill(0)
      .map((_, i) => i);
  }
  return Array(size)
    .fill(0)
    .map(() => createInitialTodos(depth - 1, size));
}

 

 

二、使用範例来比较效能

完整範例放在:https://github.com/explooosion/react-lazy-initialization

 

1. 初始 CRA 专案

使用一个乾净的专案来测试。

npx create-react-app my-app

[ App.css ]

记得先移除所有样式。

 

2. 建立两种 Components 比较

沿用刚刚昂贵计算的function createInitialTodos()

 

建立一个直接传递初始值的 ComponentAppWithDirectValue

[ App.js ]

function AppWithDirectValue() {
  const [count, setCount] = useState(0);

  console.time("Direct initializer");
  const [todos, setTodos] = useState(createInitialTodos());
  console.timeEnd("Direct initializer");

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        AppWithDirectValue ({count})
      </button>
    </div>
  );
}
  • useState上下使用console.time, console.timeEnd记录时间。
  • useState直接传入值createInitialTodos
  • 使用一个button来触发re-render

 

以及建立一个传递函数进行 lazy initialization 的 Component AppWithLazyInitializer

[ App.js ]

function AppWithLazyInitializer() {
  const [count, setCount] = useState(0);

  console.time("Lazy initializer");
  const [todos, setTodos] = useState(createInitialTodos);
  console.timeEnd("Lazy initializer");

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        AppWithLazyInitializer ({count})
      </button>
    </div>
  );
}
  • 同样在useState上下使用console.time, console.timeEnd记录时间。
  • useState传入函数createInitialTodos,你也可以传入() => createInitialTodos()
  • 使用一个button来触发re-render

 

接着在 App 使用,就可以来比较啰!

[ App.js ]

export default function App() {
  return (
    <>
      <AppWithDirectValue />
      <AppWithLazyInitializer />
    </>
  );
}

 

完整代码:

[ App.js ]

// @ts-check

import React, { useState } from "react";

/**
 * @param {number} depth
 * @param {number} size
 */
function createInitialTodos(depth = 3, size = 100) {
  if (depth === 0) {
    return Array(size)
      .fill(0)
      .map((_, i) => i);
  }
  return Array(size)
    .fill(0)
    .map(() => createInitialTodos(depth - 1, size));
}

function AppWithDirectValue() {
  const [count, setCount] = useState(0);

  console.time("Direct initializer");
  const [todos, setTodos] = useState(createInitialTodos());
  console.timeEnd("Direct initializer");

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        AppWithDirectValue ({count})
      </button>
    </div>
  );
}

function AppWithLazyInitializer() {
  const [count, setCount] = useState(0);

  console.time("Lazy initializer");
  const [todos, setTodos] = useState(createInitialTodos);
  console.timeEnd("Lazy initializer");

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        AppWithLazyInitializer ({count})
      </button>
    </div>
  );
}

export default function App() {
  return (
    <>
      <AppWithDirectValue />
      <AppWithLazyInitializer />
    </>
  );
}

 

 

三、Lazy Initialization 合适的场景

✅ 适用于初始值计算量大,例如:

  • 具有庞大的值或深度的资料。
  • 複杂计算,例如来源是解析 JSON、深拷贝物件。
  • 只需要在 第一次渲染 设定状态的情境。
     

❌ 不适用于:

  • 简单的静态值,如 useState(0)useState(false)
  • 不影响效能的初始化(过度使用 Lazy Initialization 反而让代码变得难读)。

 

 

reference

  • react dev useState - avoiding-recreating-the-initial-state
  • React Hooks 总整理笔记
  • useState lazy initialization and function updates
     

 

有勘误之处,不吝指教。ob'_'ov

 

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章

5 点赞(415) 阅读(67)