react的一些性能优化

2019-08-16

shouldComponentUpdate

对于对视图没有关联,但可能更新的数据,可以用shouldComponentUpdate过滤掉,减少无所谓的更新

返回一个Boolean值,true则更新,false则不更新,默认为true

shouldComponentUpdate(nextProps,nextState){
 if(this.props.xxx !== nextProps.xxx){
      return true;
  }else {
      return false;
  }
 }

PureComponent

对组件的stateprops仅浅对比更新,即传入的是对象,单纯的更改对象属性是无法触发更新,需要更改对象的引用(对于非基本类型的对比,对比的是引用的地址块) 可以配合immutable.js使用,以达到对象更改属性时更新的效果。

Redux配合immutable.js使用

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构)。

Immutable.js了解一下? - 掘金

memo

React.memo为高阶组件。它与 React.PureComponent非常相似,但它适用于函数组件,但不适用于 class组件。 如果你的函数组件在给定相同 props的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

原理同PureComponent,但仅对props仅做浅对比。 如果要手动添加对比过程,可以在第二个参数传入自定义的对比函数。

function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /* 如果函数返回 true,就会跳过更新。*/
   if(nextProps.xxx !== prevProps.xxx){
      return false;
  }else {
      return true;
  }
}
export default React.memo(MyComponent, areEqual);

useMemo

useMemo允许你通过「记住」上一次计算结果的方式在多次渲染的之间缓存计算结果。

memo的区别是useMemo作用于计算以及组件。

  • 第一个参数是主体内容
  • 第二个参数是数组,传入要记住的参数

作用与function:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

作用于组件:

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

注意这种方式在循环中是无效的,因为 Hook调用 不能 被放在循环中。但你可以为列表项抽取一个单独的组件,并在其中调用 useMemo

useCallback

useCallbackuseMemo有些相似。它接收一个内联函数和一个数组,它返回的是一个记忆化版本的函数。

useCallback(fn, deps)相当于 useMemo(() => fn, deps)

lazy & Suspense

React 集成了lazySuspenselazy会进行代码分割,它能让你像渲染常规组件一样处理动态引入(的组件)。。 fallback属性接受任何在组件加载过程中你想展示的 React元素。你可以将 Suspense组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense组件包裹多个懒加载组件。

👇对路由组件进行代码分割、懒加载。

import React, { lazy, Suspense } from "react";
import { renderRoutes } from "react-router-config";
import { HashRouter, Redirect } from "react-router-dom";

const Page1Comp = lazy(() => import("../Page1"));
const Page1 = props => {
  return (
    <Suspense fallback={<p>loading</p>}>
      <Page1Comp {...props} />
    </Suspense>
  );
};

const Page2Comp = lazy(() => import("../Page2"));
const Page2 = props => {
  return (
    <Suspense fallback={<p>loading</p>}>
      <Page2Comp {...props} />
    </Suspense>
  );
};

const routes = [
  { path: "/", exact: true, render: () => <Redirect to={"/page1"} /> },
  { path: "/page1", component: Page1 },
  { path: "/page2", component: Page2 }
];

function App() {
  return (
    <HashRouter>
      <div className="App">
        <h1>Hello</h1>
        {renderRoutes(routes)}
      </div>
    </HashRouter>
  );
}

export default App;

Fragment

在jsx里面,会有很多无意义但必须有的的嵌套。可以使用Fragment代替减少无谓的嵌套。

render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}
// or
render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

不适用内联函数

<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} />

👆上面的函数创建了内联函数。每次调用 render 函数时都会创建一个函数的新实例,render 函数会将该函数的新实例绑定到该按钮。 改成如下👇:

export default class InlineFunctionComponent extends Component {
  setNewStateData = (event) => {
    this.setState({
      inputValue: e.target.value
    })
  }

  render() {
    return <input type="button" onClick={this.setNewStateData} />
  }
}

函数绑定在constructor完成

<input type="button" value="Click" onClick={this.handleButtonClick.bind(this)} />

每次调用 render 函数时都会创建并使用绑定到当前上下文的新函数,但在每次渲染时使用已存在的函数效率更高。优化方案如下:

constructor() {
  this.handleButtonClick = this.handleButtonClick.bind(this)
}

箭头函数与constructor函数绑定

当我们添加箭头函数时,该函数被添加为对象实例,而不是类的原型属性。这意味着如果我们多次复用组件,那么在组件外创建的每个对象中都会有这些函数的多个实例。 每个组件都会有这些函数的一份实例,影响了可复用性。此外因为它是对象属性而不是原型属性,所以这些函数在继承链中不可用。 因此箭头函数确实有其缺点。实现这些函数的最佳方法是在构造函数中绑定函数。

节流和防抖

节流函数

节流是将多次执行变成每隔一段时间执行。

/**
 * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
 *
 * @param  {function}   func      回调函数
 * @param  {number}     wait      表示时间窗口的间隔
 * @param  {object}     options   如果想忽略开始函数的的调用,传入{leading: false}。
 *                                如果想忽略结尾函数的调用,传入{trailing: false}
 *                                两者不能共存,否则函数不能执行
 * @return {function}             返回客户调用函数
 */
_.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    // 之前的时间戳
    var previous = 0;
    // 如果 options 没传则设为空对象
    if (!options) options = {};
    // 定时器回调函数
    var later = function() {
      // 如果设置了 leading,就将 previous 设为 0
      // 用于下面函数的第一个 if 判断
      previous = options.leading === false ? 0 : _.now();
      // 置空一是为了防止内存泄漏,二是为了下面的定时器判断
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      // 获得当前时间戳
      var now = _.now();
      // 首次进入前者肯定为 true
      // 如果需要第一次不执行函数
      // 就将上次时间戳设为当前的
      // 这样在接下来计算 remaining 的值时会大于0
      if (!previous && options.leading === false) previous = now;
      // 计算剩余时间
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      // 如果当前调用已经大于上次调用时间 + wait
      // 或者用户手动调了时间
       // 如果设置了 trailing,只会进入这个条件
      // 如果没有设置 leading,那么第一次会进入这个条件
      // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
      // 其实还是会进入的,因为定时器的延时
      // 并不是准确的时间,很可能你设置了2秒
      // 但是他需要2.2秒才触发,这时候就会进入这个条件
      if (remaining <= 0 || remaining > wait) {
        // 如果存在定时器就清理掉否则会调用二次回调
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // 判断是否设置了定时器和 trailing
        // 没有的话就开启一个定时器
        // 并且不能不能同时设置 leading 和 trailing
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

防抖

防抖动是将多次执行变为最后一次执行。

/**
 * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
 *
 * @param  {function} func        回调函数
 * @param  {number}   wait        表示时间窗口的间隔
 * @param  {boolean}  immediate   设置为ture时,是否立即调用函数
 * @return {function}             返回客户调用函数
 */
export function debounce(func, wait = 200, immediate = false) {
    let timer, context, args

    // 延迟执行函数
    const later = () => setTimeout(() => {
        // 延迟函数执行完毕,清空缓存的定时器序号
        timer = null
        // 延迟执行的情况下,函数会在延迟函数中执行
        // 使用到之前缓存的参数和上下文
        if (!immediate) {
            func.apply(context, args)
            context = args = null
        }
    }, wait)

    // 这里返回的函数是每次实际调用的函数
    return function (...params) {
        // 如果没有创建延迟执行函数(later),就创建一个
        if (!timer) {
            timer = later()
            // 如果是立即执行,调用函数
            // 否则缓存参数和调用上下文
            if (immediate) {
                func.apply(this, params)
            } else {
                context = this
                args = params
            }
            // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
            // 这样做延迟函数会重新计时
        } else {
            clearTimeout(timer)
            timer = later()
            context = this
            args = params
        }
    }
}

避免内联样式

用 CSS 动画代替 JavaScript 动画

服务器gzip压缩

Web Workers 处理 CPU 密集任务

整理学习自你需要掌握的21个React性能优化技巧

原文链接:segmentfault.com

上一篇:react-router-config 使用
下一篇:浏览器工作原理整理
相关教程
关注微信

扫码加入 JavaScript 社区

相关文章

首次访问,需要验证
微信扫码,关注即可
(仅需验证一次)

欢迎加入 JavaScript 社区

号内回复关键字:

回到顶部