React作者Dan说:不要忽视state下移和内容提升

本文通过实例解析如何在React中通过状态下移和内容提升策略,优化组件渲染性能,避免不必要的组件重渲染,重点介绍了使用children属性拆分组件以减少性能开销,并探讨了这些方法背后的原理和实际应用价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

把变的从不变的抽离出来

本篇文章的核心观点是 把变的从不变中抽离出来,在写代码的时候就考虑到性能,把变的从不变的抽离出来

本篇文章的例子以及延伸是来自React核心作者 Dan Abramov,以及《React技术揭秘》作者卡颂

一个具有严重渲染性能问题的组件

我们先看下以下代码

点击试试

import { useState } from 'react';

export default function App() {
  let [color, setColor] = useState('red');
  return (
    <div>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p style={{ color }}>Hello, world!</p>
      <ExpensiveTree />
    </div>
  );
}

// 耗性能的组件,input改变这个组件就会被重新渲染
function ExpensiveTree() {
  let now = performance.now();
  while (performance.now() - now < 100) {
    // Artificial delay -- do nothing for 100ms
  }
  return <p>I am a very slow component tree.</p>;
} 

上面的组件具有严重的性能问题的原因是每次我们改变input的值得时候都会触发App重新渲染,App重新渲染就会同时导致 组件<ExpensiveTree /> 重新渲染,但是 ExpensiveTree 组件又是一个非常耗时的组件,因此视觉上会有明显卡顿。

优化方案1:抽离变的组件

解决方案(state下移):把变的从不变的抽离出来,那么就代表着耗时组件 <ExpensiveTree /> 不会被重新渲染。

import "./styles.css";
import { useState } from "react";

export default function App() {
  return (
    <>
      // 将变的input抽离出来
      <Form />
      <ExpensiveTree />
    </>
  );
}

// 耗费性能的组件
function ExpensiveTree() {
  let now = performance.now();
  while (performance.now() - now < 100) {
    // Artificial delay -- do nothing for 100ms
  }
  return <p>I am a very slow component tree.</p>;
}

// 此刻input改变,只会渲染Form组件, App就不会被重新渲染
function Form() {
  let [color, setColor] = useState('red');
  return (
    <>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p style={{ color }}>Hello, world!</p>
    </>
  );
} 

前后对比

优化方案2:内容提升

当一部分state在高开销树的上层代码中使用时上述解法就无法奏效了。举个例子,如果我们将color 放到父元素div 中。 比如例子中的 <div style={{ color }}>

export default function App() {
  let [color, setColor] = useState('red');
  return (
    <div style={{ color }}>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p>Hello, world!</p>
      <ExpensiveTree />
    </div>
  );
} 

现在看起来我们似乎没办法再将不使用color的部分提取到另一个组件中了,因为这部分代码会首先包含父组件的div,然后才包含 <ExpensiveTree />。这时候无法避免使用memo了,对吗?又或者,我们也有办法避免?

export default function App() {
  return (
    <ColorPicker>
      <p>Hello, world!</p>
      <ExpensiveTree />
    </ColorPicker>
  );
}

function ColorPicker({ children }) {
  let [color, setColor] = useState("red");
  return (
    <div style={{ color }}>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      {children}
    </div>
  );
} 

我们将App组件分割为两个子组件。依赖color的代码就和color state变量一起放入ColorPicker组件里。 不关心color的部分就依然放在App组件中,然后以JSX内容的形式传递给ColorPicker,也被称为children属性。 当color变化时,ColorPicker会重新渲染。但是它仍然保存着上一次从App中拿到的相同的children属性,所以React并不会访问那棵子树。 因此,ExpensiveTree不会重新渲染。****

寓意是什么?

在你用memo或者useMemo做优化时,如果你可以从不变的部分里分割出变化的部分,那么这看起来可能是有意义的。 关于这些方式有趣的部分是他们本身并不真的和性能有关. 使用children属性来拆分组件通常会使应用程序的数据流更容易追踪,并且可以减少贯穿树的props数量。在这种情况下提高性能是锦上添花,而不是最终目标。 奇怪的是,这种模式在将来还会带来更多的性能好处。 举个例子,当服务器组件 稳定且可被采用时,我们的ColorPicker组件就可以从服务器上获取到它的children。 整个<ExpensiveTree />组件或其部分都可以在服务器上运行,即使是顶级的React状态更新也会在客户机上“跳过”这些部分。 这是memo做不到的事情!但是,这两种方法是互补的。不要忽视state下移(和内容提升!) 然后,如果这还不够,那就使用Profiler然后用memo来写吧。

这只是一个React组合模型的自然结果。它太简单了以至于得不到赏识,然而它值得更多的爱。

参考文档:overreacted.io/zh-hans/bef…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值