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。

在大多數狀況下,咱們也會將代碼以路由爲界限進行分割,來提高界面首次加載速度。

相關文章
相關標籤/搜索