乐趣区

关于react.js:React组件性能优化

组件卸载前执行清理操作

在组件卸载前进行清理操作

日常应用中定时器是最典型的例子,比方在函数组件中,useEffect钩子内返回的函数中做清理操作

function Test() {useEffect(() => {let timer = setInterval(() => {console.log("interval running");
    }, 1000);

    return () => {clearInterval(timer);
    };
  }, []);

  return <div>test</div>;
}

例子中如果在 Test 组件卸载的时候不清理定时器,控制台会始终打印interval running

应用纯组件

纯组件会对组件输出数据进行浅层比拟,以后输出与上次输出雷同,组件不会渲染。浅层比拟和 diff 比起来小号更少的性能。diff 会遍历整个 virtualDom 树。
在类组件中,继承 PureComponent, 函数组件中应用 memo 来实现纯组件。
类组件测试代码

class App extends Component {constructor() {super();
    this.state = {name: "jake",};
  }
  updateName() {setInterval(() => {this.setState({ name: "jake"});
    }, 1000);
  }

  componentDidMount() {this.updateName();
  }

  render() {
    return (
      <>
        <NormalCom name={this.state.name} />
        <PureCom name={this.state.name} />
      </>
    );
  }
}

class NormalCom extends Component {render() {console.log("normal");
    return <div>{this.props.name}</div>;
  }
}

class PureCom extends PureComponent {render() {console.log("pure com");
    return <div>{this.props.name}</div>;
  }
}

函数组件测试代码

const ShowName = memo(function ShowName(props) {console.log("render...");
  return <div>{props.name}</div>;
});

function App() {const [name] = useState("jake");
  const [index, setIndex] = useState(0);

  useEffect(() => {setInterval(() => {setIndex((prev) => prev + 1);
    }, 1000);
  }, []);

  return (
    <div>
      <ShowName name={name} />
    </div>
  );
}

console.log("render...");只打印了一次

应用类组件中 shouldComponentUpdate

shouldComponentUpdate能够进行深层比拟。编写自定义比拟逻辑,当返回值为 true 时从新渲染组件,返回 false 阻止从新渲染。
函数原型 shouldComponentUpdate(nextProps, nextState)
测试代码

export default class App extends React.Component {constructor() {super();
    this.state = {name: "jake", age: 18};
  }
  componentDidMount() {setTimeout(() => {
      this.setState({
        name: "jake",
        age: 18,
      });
    }, 1000);
  }

  shouldComponentUpdate(nextProps, nextState) {console.log(nextState);
    if (
      nextState.name !== this.state.name ||
      nextState.age !== this.state.age
    ) {return true;} else {return false;}
  }

  render() {console.log("render....");
    return (
      <div>
        {this.state.name}
        {this.state.age}
      </div>
    );
  }
}

render 办法内的打印只执行了一次

应用懒加载

应用懒加载能够缩小 bundle 文件大小,放慢加载速度
路由懒加载

const Home = lazy(() => import(/* webpackChunkName: "Home" */ "./Home"));
const List = lazy(() => import(/* webpackChunkName: "List" */ "./List"));

function App() {
  return (
    <BrowserRouter>
      <Link to="/"> 首页 </Link>
      <Link to="/list"> 列表页 </Link>
      <Switch>
        <Suspense fallback={<div>loading</div>}>
          <Route path="/" component={Home} exact />
          <Route path="/list" component={List} />
        </Suspense>
      </Switch>
    </BrowserRouter>
  );
}

network 中加载文件在路由切换时加载不同 bundle 文件

也能够在判断语句中动静判断加载模块内容

应用 Fragment 防止额定标记

React 组件中返回 jsx 如果有多个同级元素,多个同级元素必须要有一个独特的父级,在这种状况下通常都会在最外层增加一个 div,然而这样这个 div 显得无意义,当无意义的标记增多,浏览器渲染累赘会加剧。此时能够应用 Fragment,它在渲染时不会被渲染为真正的 dom 元素, 这种占位符也能够用<></> 代替

import {Fragment} from "react";
function App() {
  return (
    <div>
      <Fragment>
        <div>1</div>
        <div>2</div>
      </Fragment>
      <>
        <div>1</div>
        <div>2</div>
      </>
    </div>
  );
}

不要应用内联函数定义

应用内联函数后,render 办法每次运行都会创立该函数的新实例,导致 React 在运行 Virtual DOM 比对时,新旧函数比对不相等,导致 React 总是为元素绑定新的函数实例,而旧的函数实例要交给垃圾回收解决。应该在组件独自定义函数,将函数绑定给事件。

  <input onChange={e=>this.setState({value:e.target.value})}/>
// 改写为
    valueChange = e =>{...}
   <input onChange={this.valueChange}/>

在构造函数中进行函数 this 绑定

在类组件中应用 fn(){}这种形式定义函数,函数 this 默认指向 undefined。也就是说函数外部的 this 指向须要被更正。能够在构造函数中对函数 this 指向进行更正,也能够在行内进行更正,两者看起来没有太大区别,然而对性能影响不同

// ------------ 形式一 ------------------------------------
  constructor() {super();
    this.state = {name: "jake", age: 18};
    // 构造函数只执行一次,所以函数 this 指向更正的代码也只执行一次,效率更高
    this.handClick = this.handClick.bind(this)
  }

// ------------ 形式二 ------------------------------------
 {/* 函数属于援用数据类型,render 办法每次执行都会调用 bind 办法生成新的函数 */}
        <button onClick={this.handClick.bind(this)}></button>

类组件中的箭头函数

在类组件中应用箭头函数不存在 this 指向问题,因为箭头函数自身不绑定 this,箭头函数在 this 指向问题上占优势,同时也有不利一面。应用箭头函数时,函数被增加为类的实例属性,而不是原型数属性,如果组件被屡次重用,每个组件实例对象都将会有一个雷同的函数实例,升高函数实例的可重用性,造成资源节约。所以,更正函数外部 this 指向的最佳做法是在构造函数中应用 bind 进行绑定

防止应用内联款式

<div style ={{color:"red"}}>test</div>

应用内联 style 为元素增加款式,内联 style 会被编译为 JS 代码,color 属性会被转化为等效 CSS 款式规定,通过 JS 代码将款式映射到元素身上,JS 操作 DOM 十分慢,浏览器会耗费更多的工夫执行脚本和渲染 UI,从而减少组件渲染工夫。举荐将 CSS 文件导入款式组件,能通过 CSS 间接做的事件不要通过 JS 去做。

优化条件渲染

频繁挂载和卸载组件是一件耗费性能的操作,在实际操作中,应该缩小组件挂载和卸载次数。
代码中经常出现须要利用一个变量的 turefalse值来判断某个组件是否应该被渲染就是罕用案例。

return (
    <>
        {show && <shouwComponent/>}
        <shouwComponentA/>
        <shouwComponentB/>
    </>
)

防止反复有限渲染

class App extends React.Component {constructor() {super();
    this.state = {name: "jake"};
  }
  render() {this.setState({ name: "tony"});
    return <div>{this.state.name}</div>;
  }
}


在应用程序状态产生更改时,React 会调用 render 办法,如果在 render 办法中持续更改应用程序状态,就会产生 render 办法递归调用导致利用报错。
下面例子报错不能再 componentWillUpdate componentDidUpdate render 办法中调用setState。源码中对反复调用做了限度,为 50 次。超过 50 次就报错。render 办法应该被作为纯函数,在 render 办法中不要调用 setState 不要应用其余伎俩查问更改原生 DOM 元素,以及其余更改应用程序的任何操作。render 办法的执行要依据状态的扭转,这样能够放弃组件的行为和渲染形式统一

为组件创立谬误边界

默认状况下,组件渲染谬误会导致整个程序中断,创立谬误边界能够确保在特定组件产生谬误时程序不会中断,减少程序的健壮性。
谬误边界是一个 Reac 组件,能够捕捉子组件在渲染时产生的谬误,当谬误产生时,能够将谬误记录下来,或者显示备用 UI 界面。谬误边界波及两个生命周期函数,getDerivedStateFromErrorcomponentDidCatch
getDerivedStateFromError 为静态方法,返回一个对象,返回对象会和 state 对象进行合并,用于更改应用程序状态,显示备用 UI 界面
componentDidCatch 用于记录应用程序错误信息,参数就是谬误对象
谬误边界不能捕捉异步谬误,如:点击按钮产生的谬误
谬误边界类

import React from "react";
import App from "./App";

export default class ErrorBoundaries extends React.Component {constructor() {super();
    this.state = {hasError: false,};
  }

  componentDidCatch(error) {console.log("componentDidCatch");
    console.log(error);
  }

  static getDerivedStateFromError() {console.log("getDerivedStateFromError");
    return {hasError: true,};
  }

  render() {if (this.state.hasError) {return <div>error occur</div>;}
    return <App />;
  }
}

app 类中抛出异样

function App() {throw new Error("error happen");
  return <div>app</div>;
}

界面会打印出须要出现的预期谬误后果

防止数据结构渐变

组件中 props 和 state 数据结构应该放弃一致性,数据结构渐变会导致输入不统一,如果不留神这种问题,可能会呈现一些让人感到莫名的谬误,数据结构简单之后,排查起来也会减少难度。

优化依赖项大小

程序中常常会依赖第三方包,然而不想援用包中所有代码,只须要局部即可,此时能够应用插件对依赖项进行优化

yarn add react-app-rewired customize-cra lodash babel-plugin-lodash

react-app-rewired笼罩 create-react-app 的默认配置
customize-cra 导出一些辅助办法,让代码更为简洁
babel-plugin-lodash 对利用中的 lodash 进行精简

没做任何操作时,打包大小为

1. 引入lodash, 而后打包

import _ from "lodash"

function App() {console.log(_.chunk(['a','b','c','d'],2))
  return <div>app</div>;
}


2, 开始优化操作,根目录下新建config-overrides.js
override 能够接管多个参数,每个参数都是一个配置函数,函数构造 oldConfig,返回 newConfig
useBabelRc: 容许应用.babelrc 文件进行 babel 配置

const {override, useBabelRc} = require("customize-cra");

// eslint-disable-next-line react-hooks/rules-of-hooks
module.exports = override(useBabelRc());

3,新建 .babelrc 配置

{"plugins": ["lodash"]
}

4, 批改 package.json 配置

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test --env=jsdom",
    "eject": "react-scripts eject"
  },

而后打包

包体积减小

退出移动版