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 */
});
如果 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);
对于依赖功能组件来渲染低级 UI 元素的组件和设计系统来说,这是一个巨大的性能提升。
回调“缓存”
还实现了一个新的钩子,它在函数上使用相同的记忆逻辑。它可以防止函数被再次调用,除非它的参数(或你指定的变量)发生变化:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
悬念结束了🌟
我首先想深入研究的是 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>
);
}
我不仅可以延迟组件包的加载(从而加快应用的初始加载速度),还可以插入任何正在加载的组件。这使得像骨架屏这样的效果变得轻而易举。
钩子
最近,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>
)
}
为了处理组件中的任何副作用,请在useEffect()
功能组件内部加入一个代码,以便在每次状态改变/重新渲染时运行代码。
Hooks 最大的优点之一就是其函数式特性(FP FTW)。你可以将 Hook 及其效果提取到一个单独的函数中,并在应用中的多个组件中复用该 Hook。
Hooks = 更少的编译代码
添加钩子函数的一大优点是,它能够让你放弃类,转而使用状态逻辑,从而支持更高效的函数。如果你曾经看过大多数编译好的 JS 代码,就会发现,由于类的工作方式(相当于原型的语法糖),在应用中使用类会导致代码臃肿,并产生大量的 polyfill。
本课程:
class Test extends React {
constructor() {
super()
this.state = {}
}
render() {
return <div>Test</div>
}
}
编译为:
"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);
相反,如果你使用函数(除非是 ES6 箭头函数),它会像看起来那样编译通过——因为函数被广泛支持(因为它是如此原始/早期的 JS API)。即使考虑到数组解构,代码仍然比 class 少,同时能够使用状态:
function Test(props) {
const [counter, increment] = useState(0);
return <h1>Hello</h1>;
}
"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"
);
}
React 更具组合性的未来
很高兴看到 React API 在过去一年里取得了长足的进步。团队在维护旧版 API 和避免应用程序崩溃方面做得非常出色(Facebook 仍在使用React.createElement
),而且新增的功能也都解决了开发者遇到的关键问题。我都记不清有多少次为了处理一个有状态的布尔值,不得不把一个函数式组件转换成类,而现在我只需在函数顶部添加一个钩子(并记录它,就能获得与 PureComponent 相同的性能!)。
干杯
🍻Ryo
参考:
文章来源:https://dev.to/whoisryosuke/react-hooks-suspense-and-memo-513o