likes
comments
collection
share

React组件性能优化

作者站长头像
站长
· 阅读数 31

组件卸载前执行清理操作

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

日常使用中定时器是最典型的例子,比如在函数组件中 ,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>;
  }
}

React组件性能优化

函数组件测试代码

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...");只打印了一次React组件性能优化

使用类组件中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方法内的打印只执行了一次React组件性能优化

使用懒加载

使用懒加载可以减少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文件React组件性能优化也可以在判断语句中动态判断加载模块内容

使用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>
  );
}

React组件性能优化

不要使用内联函数定义

使用内联函数后,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组件性能优化在应用程序状态发生更改时,React会调用render方法,如果在render方法中继续更改应用程序状态,就会发生render方法递归调用导致应用报错。上面例子报错不能再componentWillUpdate componentDidUpdate render方法中调用setState。源码中对重复调用做了限制,为50次。超过50次就报错。render方法应该被作为纯函数,在render方法中不要调用setState不要使用其他手段查询更改原生DOM元素,以及其他更改应用程序的任何操作。render方法的执行要根据状态的改变,这样可以保持组件的行为和渲染方式一致

为组件创建错误边界

默认情况下,组件渲染错误会导致整个程序中断,创建错误边界可以确保在特定组件发生错误时程序不会中断,增加程序的健壮性。错误边界是一个Reac组件,可以捕获子组件在渲染时发生的错误,当错误发生时,可以将错误记录下来,或者显示备用UI界面。错误边界涉及两个生命周期函数,getDerivedStateFromErrorcomponentDidCatchgetDerivedStateFromError为静态方法,返回一个对象,返回对象会和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>;
}

界面会打印出需要呈现的预期错误结果React组件性能优化

避免数据结构突变

组件中props和state数据结构应该保持一致性,数据结构突变会导致输出不一致,如果不注意这种问题,可能会出现一些让人感到莫名的错误,数据结构复杂之后,排查起来也会增加难度。

为列表数据添加唯一标识

渲染列表是,为每一项添加key属性,key属性的值必须是唯一的,(没有唯一标识可以用索引代替,仅限静态列表)key属性可以让React直接了解知道哪些列表项发生了变化,从而避免React内部逐一遍历Virtual DOM查找变化所带来的的性能消耗,避免元素因为位置变化而导致的重新创建

优化依赖项大小

程序中经常会依赖第三方包,但是不想引用包中所有代码,只需要部分即可,此时可以使用插件对依赖项进行优化

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

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

没做任何操作时,打包大小为React组件性能优化

1.引入lodash,然后打包

import _ from "lodash"

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

React组件性能优化2,开始优化操作,根目录下新建config-overrides.jsoverride可以接收多个参数,每个参数都是一个配置函数,函数结构oldConfig,返回newConfiguseBabelRc:允许使用.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"
  },

然后打包React组件性能优化包体积减小