React性能优化

在咱们日常的 React 开发中,咱们肆无忌惮的编写着代码,项目刚开始可能没什么大问题,可是随着项目愈来愈大,功能愈来愈多,问题就慢慢的就凸显出来了。为了让项目可以正常、稳定的运行,咱们在编写代码的时候应该多思考,代码该怎么划分、该怎么编写。javascript

如下为总结的一些关于react优化相关的知识:java

多用React.PureComponent和React.memo

React.PureComponentReact.memo很类似,都是可以在必定的条件下减小组件的从新渲染,提升性能。react

React.PureComponent

React.PureComponentReact.Component很类似,二者的区别在于 React.Component并未实现shouldComponentUpdate(),而React.PureComponent中以浅层对比 prop 和 state 的方式来实现了shouldComponentUpdate(),因此当新的 props 或 state 出现的时候,React.PureComponent会自行将新的 props 和 state 与原来的进行浅对比,若是没有变化的话,组件就不会从新render。ios

注意 React.PureComponent仅仅是作浅对比,若是 props 或者 state 有比较复杂的结构好比数组或者对象的话,这个时候比较容易出错,须要你在深层数据结构发生变化时调用forceUpdate()来确保组件被正确地更新。你也能够考虑使用 immutable 对象加速嵌套数据的比较。
React.PureComponent中若是编写了自定义的shouldComponentUpdate()React.PureComponent将会取消默认的浅层对比,而使用自定义的shouldComponentUpdate()算法

class RegularChildComponent extends Component<IProps, IState> {
    render() {
        console.log("Regular Component Rendered.."); // 每次父组件更新都会渲染
        return <div>{this.props.name}</div>;
    }
}

class PureChildComponent extends PureComponent<IProps, IState> {

  readonly state: Readonly<IState> = {
    age: "18",
  }

  updateState = () => {
    setInterval(() => {
      this.setState({
        age: "18"
      })
    }, 1000)
  }

  componentDidMount() {
    this.updateState();
  }

  render() {
    console.log("Pure Component Rendered..")  // 只渲染一次
    return <div>{this.props.name}</div>;
  }
}

class Demo extends Component<IProps, IState> {
  readonly state: Readonly<IState> = {
    name: "liu",
  }

  componentDidMount() {
    this.updateState();
  }

  updateState = () => {
    setInterval(() => {
      this.setState({
        name: "liu"
      })
    }, 1000)
  }

  render() {
    console.log("Render Called Again") // 每次组件更新都会渲染
    return (
      <div>
        <RegularChildComponent name={'this.state.name'} />
        <PureChildComponent name={this.state.name} />
      </div>
    )
  }
}
复制代码

React.memo(component, areEqual)

React.memoReact.PureComponent功能差很少,可是React.memo适用于函数组件,但不适用于 class 组件。数组

若是你的函数组件在给定相同 props 的状况下渲染相同的结果,那么你能够经过将其包装在React.memo中调用,以此经过记忆组件渲染结果的方式来提升组件的性能表现。这意味着在这种状况下,React 将跳过渲染组件的操做并直接复用最近一次渲染的结果。数据结构

默认状况下其只会对复杂对象作浅层对比,若是你想要控制对比过程,那么请将自定义的比较函数经过第二个参数传入来实现。函数

// 父组件和上面同样
function ChildComponent(props: IProps){
  console.log("Pure Component Rendered..")  // 只渲染一次
  return <div>{props.name}</div>;
}
const PureChildComponent = React.memo(ChildComponent);
复制代码

巧用shouldComponentUpdate(nextProps, nextState)

在这个函数里面,咱们能够本身来决定是否从新渲染组件,返回 false 以告知 React 能够跳过更新。首次渲染或使用 forceUpdate() 时不会调用该方法。性能

注意 不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样很是影响效率,且会损害性能。
返回 false 并不会阻止子组件在 state 更改时从新渲染。优化

class Demo extends Component<IProps, IState> {
  readonly state: Readonly<IState> = {
    name: "liu",
    age: 18,
  }

  componentDidMount() {
    this.setState({
      name: "liu",
      age: 19,
    })
  }

  shouldComponentUpdate(nextProps: IProps, nextState: IState){
    if(this.state.name !== nextState.name){
      return true;
    }
    return false;
  }

  render() {
    console.log("Render Called Again") // 只会打印一次
    return (
      <div> {this.state.name} </div>
    )
  }
}
复制代码

上面的例子中,由于组件只渲染了 state.name ,因此当 age 改变可是 name 没有改变的时候咱们并不须要从新渲染,因此咱们能够在shouldComponentUpdate()中阻止渲染。在上面例子中阻止渲染是对的,可是当前组件或子组件若是用到了 state.age ,那么咱们就不能只根据 name 来判断是否阻止渲染,不然会出现数据和界面不一致的状况。

所以,当咱们在使用shouldComponentUpdate()应该始终清楚咱们什么状况下才能阻止从新渲染。

bind函数位置

当咱们在 React 中建立函数时,咱们须要使用 bind 关键字将函数绑定到当前上下文。绑定能够在构造函数中完成,也能够在咱们将函数绑定到 DOM 元素的位置上完成。

可是当咱们将函数绑定到 DOM 元素的位置后,每次render的时候都会进行一次bind,这将会有一些没必要要的性能损耗,并且还有可能致使子组件没必要要的渲染。因此咱们能够在构造函数中绑定,也能够直接写箭头函数。同理,咱们尽可能不写内联函数和内联属性。

// good
class Binding extends React.Component<IProps, IState> {
  constructor() {
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    alert("Button Clicked")
  }
  
  render() {
    return (
      <input type="button" value="Click" onClick={this.handleClick} />
    )
  }
}
// good
class Binding extends React.Component<IProps, IState> {
  handleClick = () => {
    alert("Button Clicked")
  }
  
  render() {
    return (
      <input type="button" value="Click" onClick={this.handleClick} />
    )
  }
}
// bad
class Binding extends React.Component<IProps, IState> {
  handleClick() {
    alert("Button Clicked")
  }
  
  render() {
    return (
      <input type="button" value="Click" onClick={this.handleClick.bind(this)} />
    )
  }
}
复制代码

使用key

key 帮助 React 识别哪些元素改变了,好比被添加或删除。所以你应当给数组中的每个元素赋予一个肯定的标识。由于有key的存在,使得 React 的diff算法有质的飞跃。

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。一般,咱们使用数据中的 id 来做为元素的 key。若是列表项目的顺序可能会变化,咱们不建议使用索引来用做 key 值,由于这样作会致使性能变差,还可能引发组件状态的问题

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);
复制代码

代码分割

当咱们在一个界面有两个或多个互斥或者不太可能同时出现的”大组件“的时候,咱们能够将那些组件分割出来,以减小主js的体积。

// 该组件是动态加载的
const OneComponent = React.lazy(() => import('./OneComponent'));
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  let isIOS = getCurrentEnv();
  return (
    // 显示 <Spinner> 组件直至 OneComponent 或 OtherComponent 加载完成
    <React.Suspense fallback={<Spinner />}>
      <div>
        {
          isIOS ? <OneComponent /> : <OtherComponent />>
        }
      </div>
    </React.Suspense>
  );
}
复制代码

上面的代码示例中, OneComponent 和 OtherComponent 是两个比较大的组件,在ios环境下咱们渲染 OneComponent,在非ios环境下渲染 OtherComponent,正常状况下两个组件只会被加载其中的一个,因此咱们能够将代码分割出来,保证主js不会包括不须要用到的js。

在大多数状况下,咱们也会将代码以路由为界限进行分割,来提高界面首次加载速度。

相关文章
相关标签/搜索