不一样类型业务要求的性能标准各不相同。若是对一个 ToB 的后台管理系统要求首屏速度以及 SEO,显然不合理也不必。html
第一要考虑的不是如何去优化,而是值不值得去优化,React 性能已经足够优秀,毕竟“过早优化是魔鬼”,状况老是“能够,但不必”。vue
做为一个开发人员,深刻了解工具不足之处,并拥有对其进行优化的能力,是极其重要的。react
React 性能优化大抵可分为两点:git
本文基于 memoize-one 对 render 方法进行优化,达到减轻没必要要 render 复杂度的效果。github
先看一个简单的组件,以下所示:编程
class Example extends Component {
state = {
filterText: ""
};
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
const filteredList = this.props.list.filter(item =>
item.text.includes(this.state.filterText)
);
return (
<Fragment> <input onChange={this.handleChange} value={this.state.filterText} /> <ul> {filteredList.map(item => ( <li key={item.id}>{item.text}</li> ))} </ul> </Fragment> ); } } 复制代码
该组件接收父组件传递的 list,筛选出包含 filterText 的 filteredList 并进行展现。缓存
问题是什么?性能优化
在未进行任何处理的状况下,父组件 render,总会致使子组件 render,即便子组件的 state/props 并未发生变化,若是筛选的数据量大,筛选逻辑复杂,这将是一个很重要的优化点。闭包
要达到怎样的效果?app
A memoization library which only remembers the latest invocation
import memoize from "memoize-one";
const add = (a, b) => a + b; // 基本计算方法
const memoizedAdd = memoize(add); // 生成可缓存的计算方法
memoizedAdd(1, 2); // 3
memoizedAdd(1, 2); // 3
// Add 函数没有被执行:上一次的结果直接返回
memoizedAdd(2, 3); // 5
// Add 函数被调用获取新的结果
memoizedAdd(2, 3); // 5
// Add 函数没有被执行:上一次的结果直接返回
memoizedAdd(1, 2); // 3
// Add 函数被调用获取新的结果
// 即便该结果在以前已经缓存过了
// 但它并非最近一次的缓存结果,因此缓存结果丢失了
复制代码
在了解基本使用后,咱们来对上述案例进行优化。
import memoize from "memoize-one";
class Example extends Component {
state = { filterText: "" };
// 只有在list或filterText改变的时候才会从新调用真正的filter方法(memoize入参)
filter = memoize((list, filterText) =>
list.filter(item => item.text.includes(filterText))
);
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
// 在上一次render后,若是参数没有发生改变,`memoize-one`会重复使用上一次的返回结果
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<Fragment> <input onChange={this.handleChange} value={this.state.filterText} /> <ul> {filteredList.map(item => ( <li key={item.id}>{item.text}</li> ))} </ul> </Fragment> ); } } 复制代码
若是除去 ts 相关以及注释,不到 20 行。memoize-one 本质是一个高阶函数,真正计算函数做为参数,返回一个新的函数,新的函数内部会缓存上一次入参以及上一次返回值,若是本次入参与上一次入参相等,则返回上一次返回值,不然,从新调用真正的计算函数,并缓存入参以及结果,供下一次使用。
伪装这里有一张流程图 :)
// 默认比较前后入参是否相等的方法,使用者可自定义比较方法
import areInputsEqual from './are-inputs-equal';
// 函数签名
export default function<ResultFn: (...any[]) => mixed>( resultFn: ResultFn, isEqual?: EqualityFn = areInputsEqual, ): ResultFn {
// 上一次的this
let lastThis: mixed;
// 上一次的参数
let lastArgs: mixed[] = [];
// 上一次的返回值
let lastResult: mixed;
// 是否已经初次调用过了
let calledOnce: boolean = false;
// 被返回的函数
const result = function(...newArgs: mixed[]) {
// 若是参数或this没有发生变化或非初次调用
if (calledOnce && lastThis === this && isEqual(newArgs, lastArgs)) {
// 直接返回上一次的计算结果
return lastResult;
}
// 参数发生变化或者是初次调用
lastResult = resultFn.apply(this, newArgs);
calledOnce = true;
// 保存当前参数
lastThis = this;
// 保存当前结果
lastArgs = newArgs;
// 返回当前结果
return lastResult;
};
// 返回新的函数
return (result: any);
}
复制代码
另可使用decko这个库,内置bind/memoize/debounce三个装饰器,与React契合度很高。
下面是一个计算斐波那契数列的例子,该例子使用迭代代替递归,而且利用闭包缓存以前的结果。
const createFab = () => {
const cache = [0, 1, 1];
return n => {
if (typeof cache[n] !== "undefined") {
return cache[n];
}
for (let i = 3; i <= n; i++) {
if (typeof cache[i] !== "undefined") continue;
cache[i] = cache[i - 1] + cache[i - 2];
}
return cache[n];
};
};
const fab = createFab();
复制代码
本文基于 React 介绍了 memoize-one 库的相关使用及其原理,在 React 中实现了相似与 Vue 计算属性(computed)的效果 —— 基于依赖缓存计算结果,达到减轻没必要要 render 复杂度的效果。
从业务开发角度来说,Vue 提供的 API 极大地提升了开发效率。
React 自身解决的问题并很少,但得益于活跃的社区,工做中遇到的解决问题都能找到解决方案,而且在摸索这些解决方案的同时,咱们可以学习到诸多经典的编程思想,从而减轻对框架的依赖。
I’ve always said that React will make you a better JavaScript developer. - Tyler McGinnis