React Hooks、Suspense 和 Memo

2025-06-07

React Hooks、Suspense 和 Memo

编者注:我在 Hooks 仍处于 alpha 版本(React v16.6)时写了这篇文章,并决定在 Hooks 正式发布(v16.8)后完成它。

最近 React 社区真是热闹非凡!几个月来,我们一直提心吊胆React.memo(),现在,Create React App v2、Hooks、Memo 等新老 React 开发者们都忙得不可开交,各种新玩意儿也纷纷涌现。我终于有时间深入研究一下新的、React.lazy()<Suspense />API,以及即将发布的 Hooks API。

PureComponent 用于功能组件

memoize! 新增了一种技术,React.memo()它是一种 HOC,如果 props 相同,则可以防止组件在 props 发生变化时进行渲染。它本质上是在shouldComponentUpdate()生命周期中对 props 进行浅层相等操作,但适用于无法访问 props 的函数式组件(无需切换到类)。

const MyComponent = React.memo(function MyComponent(props) {
  /* render using props */
});
Enter fullscreen mode Exit fullscreen mode

如果 props 包含复杂的对象,我们可以在组件内部添加一个函数来检查:

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);
Enter fullscreen mode Exit fullscreen mode

对于依赖功能组件来渲染低级 UI 元素的组件和设计系统来说,这是一个巨大的性能提升。

回调“缓存”

还实现了一个新的钩子,它在函数上使用相同的记忆逻辑。它可以防止函数被再次调用,除非它的参数(或你指定的变量)发生变化:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Enter fullscreen mode Exit fullscreen mode

悬念结束了🌟

我首先想深入研究的是 Suspense,因为它实际上已经实现了(即使不是未完成的)。在 3 月份的 ReactFest 2018 上观看了 Dan 关于 Suspense 的精彩演讲后,我很高兴看到 React 已经将延迟加载视为优先事项,并将其整合到他们的 API 中。我不再依赖react-loadable 之类的库或 Webpack 中的配置,而是可以简单地:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

我不仅可以延迟组件包的加载(从而加快应用的初始加载速度),还可以插入任何正在加载的组件。这使得像骨架屏这样的效果变得轻而易举。

您可以在 CodeSandbox 上看到一个实例

钩子

最近,React 提出了一种新的、更实用的状态处理方式,即使用“hooks”,而不是依赖于 React 组件的生命周期方法。你可以在 React 文档中找到完整的提案

使用它们很简单,并且与类替代方案相比,它们提供了具有功能组件的较低 LOC。

function YourComponent({ text }) {
  const [ theText, updateText] = useState(text)
  const changeText = ({ target: { value } }) => {
    updateText(value)
  }
  return(
    <button onClick={() => changeText}>
      {theText}
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

为了处理组件中的任何副作用,请在useEffect()功能组件内部加入一个代码,以便在每次状态改变/重新渲染时运行代码。

Hooks 最大的优点之一就是其函数式特性(FP FTW)。你可以将 Hook 及其效果提取到一个单独的函数中,并在应用中的多个组件中复用该 Hook。

Hooks = 更少的编译代码

添加钩子函数的一大优点是,它能够让你放弃类,转而使用状态逻辑,从而支持更高效的函数。如果你曾经看过大多数编译好的 JS 代码,就会发现,由于类的工作方式(相当于原型的语法糖),在应用中使用类会导致代码臃肿,并产生大量的 polyfill。

本课程:

class Test extends React {
  constructor() {
    super()
    this.state = {}
  }
  render() {
    return <div>Test</div>
  }
}
Enter fullscreen mode Exit fullscreen mode

编译为:

"use strict";

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Test = function (_React) {
  _inherits(Test, _React);

  function Test() {
    _classCallCheck(this, Test);

    var _this = _possibleConstructorReturn(this, (Test.__proto__ || Object.getPrototypeOf(Test)).call(this));

    _this.state = {};
    return _this;
  }

  _createClass(Test, [{
    key: "render",
    value: function render() {
      return React.createElement(
        "div",
        null,
        "Test"
      );
    }
  }]);

  return Test;
}(React);
Enter fullscreen mode Exit fullscreen mode

相反,如果你使用函数(除非是 ES6 箭头函数),它会像看起来那样编译通过——因为函数被广泛支持(因为它是如此原始/早期的 JS API)。即使考虑到数组解构,代码仍然比 class 少,同时能够使用状态:

function Test(props) {
  const [counter, increment] = useState(0);
  return <h1>Hello</h1>;
}
Enter fullscreen mode Exit fullscreen mode
"use strict";

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

function Test(props) {
  var _useState = useState(0),
      _useState2 = _slicedToArray(_useState, 2),
      counter = _useState2[0],
      increment = _useState2[1];

  return React.createElement(
    "h1",
    null,
    "Hello"
  );
}
Enter fullscreen mode Exit fullscreen mode

React 更具组合性的未来

很高兴看到 React API 在过去一年里取得了长足的进步。团队在维护旧版 API 和避免应用程序崩溃方面做得非常出色(Facebook 仍在使用React.createElement),而且新增的功能也都解决了开发者遇到的关键问题。我都记不清有多少次为了处理一个有状态的布尔值,不得不把一个函数式组件转换成类,而现在我只需在函数顶部添加一个钩子(并记录它,就能获得与 PureComponent 相同的性能!)。

干杯
🍻Ryo


参考

文章来源:https://dev.to/whoisryosuke/react-hooks-suspense-and-memo-513o
PREV
使用 Gatsby 和 MDX 重新设计我的博客
NEXT
足够的 JavaScript 入门指南:#14 理解 DOM