1. 程式人生 > 其它 >React效能優化的8種方式

React效能優化的8種方式

react憑藉virtual DOM和diff演算法擁有高效的效能,除此之外也有很多其他的方法和技巧可以進一步提升react效能,在本文中我將列舉出可有效提升react效能的幾種方法,幫助我們改進react程式碼,提升效能。但是我們不必一定要在專案中使用這些方法,但是我們有必要知道如何使用這些方法。

使用React.Memo來快取元件

提升應用程式效能的一種方法是實現memoization。Memoization是一種優化技術,主要通過儲存昂貴的函式呼叫的結果,並在再次發生相同的輸入時返回快取的結果,以此來加速程式。
父元件的每次狀態更新,都會導致子元件重新渲染,即使傳入子元件的狀態沒有變化,為了減少重複渲染,我們可以使用React.memo來快取元件,這樣只有當傳入元件的狀態值發生變化時才會重新渲染。如果傳入相同的值,則返回快取的元件。示例如下:

export default React.memo((props) => {
  return (
    <div>{props.value}</div>  
  )
});

使用useMemo快取大量的計算

有時渲染是不可避免的,但如果您的元件是一個功能元件,重新渲染會導致每次都呼叫大型計算函式,這是非常消耗效能的,我們可以使用新的useMemo鉤子來“記憶”這個計算函式的計算結果。這樣只有傳入的引數發生變化後,該計算函式才會重新呼叫計算新的結果。
通過這種方式,您可以使用從先前渲染計算的結果來挽救昂貴的計算耗時。總體目標是減少JavaScript在呈現元件期間必須執行的工作量,以便主執行緒被阻塞的時間更短。

// 避免這樣做
function Component(props) {
  const someProp = heavyCalculation(props.item);
  return <AnotherComponent someProp={someProp} /> 
}
  
// 只有 `props.item` 改變時someProp的值才會被重新計算
function Component(props) {
  const someProp = useMemo(() => heavyCalculation(props.item), [props.item]);
  return <AnotherComponent someProp={someProp} /> 
}

使用React.PureComponent,shouldComponentUpdate

父元件狀態的每次更新,都會導致子元件的重新渲染,即使是傳入相同props。但是這裡的重新渲染不是說會更新DOM,而是每次都會呼叫diif演算法來判斷是否需要更新DOM。這對於大型元件例如元件樹來說是非常消耗效能的。
在這裡我們就可以使用React.PureComponent,shouldComponentUpdate生命週期來確保只有當元件props狀態改變時才會重新渲染。如下例子:

export default function ParentComponent(props) {
  return (
    <div>
      <SomeComponent someProp={props.somePropValue}
    <div>
      <AnotherComponent someOtherProp={props.someOtherPropValue} />
    </div>
   </div>
 )
}

export default function SomeComponent(props) {
  return (
    <div>{props.someProp}</div>  
  )
}

// 只要props.somePropValue 發生變化,不論props.someOtherPropValue是否發生變化該元件都會發生變化
export default function AnotherComponent(props) {
  return (
    <div>{props.someOtherProp}</div>  
  )
}

我們可以使用React.PureComponent或shouldComponentUpdate進行如下優化:

// 第一種優化
class AnotherComponent extends React.PureComponent {
  render() {
    return <div>{this.props.someOtherProp}</div>   
  }
}

//第二種優化
class AnotherComponent extends Component {
  shouldComponentUpdate(nextProps) {
    return this.props !== nextProps
  }
  render() {
    return <div>{this.props.someOtherProp}</div>   
  }
}


PureComponent會進行淺比較來判斷元件是否應該重新渲染,對於傳入的基本型別props,只要值相同,淺比較就會認為相同,對於傳入的引用型別props,淺比較只會認為傳入的props是不是同一個引用,如果不是,哪怕這兩個物件中的內容完全一樣,也會被認為是不同的props。
需要注意的是在對於那些可以忽略渲染時間的元件或者是狀態一直變化的元件則要謹慎使用PureComponent,因為進行淺比較也會花費時間,這種優化更適用於大型的展示元件上。大型元件也可以拆分成多個小元件,並使用memo來包裹小元件,也可以提升效能。

避免使用內聯物件

使用內聯物件時,react會在每次渲染時重新建立對此物件的引用,這會導致接收此物件的元件將其視為不同的物件,因此,該元件對於prop的淺層比較始終返回false,導致元件一直重新渲染。
許多人使用的內聯樣式的間接引用,就會使元件重新渲染,可能會導致效能問題。為了解決這個問題,我們可以保證該物件只初始化一次,指向相同引用。另外一種情況是傳遞一個物件,同樣會在渲染時建立不同的引用,也有可能導致效能問題,我們可以利用ES6擴充套件運算子將傳遞的物件解構。這樣元件接收到的便是基本型別的props,元件通過淺層比較發現接受的prop沒有變化,則不會重新渲染。示例如下:

// Don't do this!
function Component(props) {
  const aProp = { someProp: 'someValue' }
  return <AnotherComponent style={{ margin: 0 }} aProp={aProp} />  
}

// Do this instead :)
const styles = { margin: 0 };
function Component(props) {
  const aProp = { someProp: 'someValue' }
  return <AnotherComponent style={styles} {...aProp} />  
}

避免使用匿名函式

雖然匿名函式是傳遞函式的好方法(特別是需要用另一個prop作為引數呼叫的函式),但它們在每次渲染上都有不同的引用。這類似於上面描述的內聯物件。為了保持對作為prop傳遞給React元件的函式的相同引用,您可以將其宣告為類方法(如果您使用的是基於類的元件)或使用useCallback鉤子來幫助您保持相同的引用(如果您使用功能元件)。前端培訓
當然,有時內聯匿名函式是最簡單的方法,實際上並不會導致應用程式出現效能問題。這可能是因為在一個非常“輕量級”的元件上使用它,或者因為父元件實際上必須在每次props更改時重新渲染其所有內容。因此不用關心該函式是否是不同的引用,因為無論如何,元件都會重新渲染。

// 避免這樣做
function Component(props) {
  return <AnotherComponent onChange={() => props.callback(props.id)} />  
}

// 優化方法一
function Component(props) {
  const handleChange = useCallback(() => props.callback(props.id), [props.id]);
  return <AnotherComponent onChange={handleChange} />  
}

// 優化方法二
class Component extends React.Component {
  handleChange = () => {
   this.props.callback(this.props.id) 
  }
  render() {
    return <AnotherComponent onChange={this.handleChange} />
  }
}

延遲載入不是立即需要的元件

延遲載入實際上不可見(或不是立即需要)的元件,React載入的元件越少,載入元件的速度就越快。因此,如果您的初始渲染感覺相當粗糙,則可以在初始安裝完成後通過在需要時載入元件來減少載入的元件數量。同時,這將允許使用者更快地載入您的平臺/應用程式。最後,通過拆分初始渲染,您將JS工作負載拆分為較小的任務,這將為您的頁面提供響應的時間。這可以使用新的React.Lazy和React.Suspense輕鬆完成。

// 延遲載入不是立即需要的元件
const MUITooltip = React.lazy(() => import('@material-ui/core/Tooltip'));
function Tooltip({ children, title }) {
  return (
    <React.Suspense fallback={children}>
      <MUITooltip title={title}>
        {children}
      </MUITooltip>
    </React.Suspense>
  );
}

function Component(props) {
  return (
    <Tooltip title={props.title}>
      <AnotherComponent />
    </Tooltip>
  )
}

調整CSS而不是強制元件載入和解除安裝

渲染成本很高,尤其是在需要更改DOM時。每當你有某種手風琴或標籤功能,例如想要一次只能看到一個專案時,你可能想要解除安裝不可見的元件,並在它變得可見時將其重新載入。如果載入/解除安裝的元件“很重”,則此操作可能非常消耗效能並可能導致延遲。在這些情況下,最好通過CSS隱藏它,同時將內容儲存到DOM。
儘管這種方法並不是萬能的,因為安裝這些元件可能會導致問題(即元件與視窗上的無限分頁競爭),但我們應該選擇在不是這種情況下使用調整CSS的方法。另外一點,將不透明度調整為0對瀏覽器的成本消耗幾乎為0(因為它不會導致重排),並且應儘可能優先於更該visibility 和 display。
有時在保持元件載入的同時通過CSS隱藏可能是有益的,而不是通過解除安裝來隱藏。對於具有顯著的載入/解除安裝時序的重型元件而言,這是有效的效能優化手段。

// 避免對大型的元件頻繁對載入和解除安裝
function Component(props) {
  const [view, setView] = useState('view1');
  return view === 'view1' ? <SomeComponent /> : <AnotherComponent />  
}

// 使用該方式提升效能和速度
const visibleStyles = { opacity: 1 };
const hiddenStyles = { opacity: 0 };
function Component(props) {
  const [view, setView] = useState('view1');
  return (
    <React.Fragment>
      <SomeComponent style={view === 'view1' ? visibleStyles : hiddenStyles}>
      <AnotherComponent style={view !== 'view1' ? visibleStyles : hiddenStyles}>
    </React.Fragment>
  )
}

使用React.Fragment避免新增額外的DOM

有些情況下,我們需要在元件中返回多個元素,例如下面的元素,但是在react規定元件中必須有一個父元素。

            <h1>Hello world!</h1>
            <h1>Hello there!</h1>
            <h1>Hello there again!</h1>
複製程式碼

因此你可能會這樣做,但是這樣做的話即使一切正常,也會建立額外的不必要的div。這會導致整個應用程式內建立許多無用的元素:

function Component() {
        return (
            <div>
                <h1>Hello world!</h1>
                <h1>Hello there!</h1>
                <h1>Hello there again!</h1>
            </div>
        )
}
複製程式碼

實際上頁面上的元素越多,載入所需的時間就越多。為了減少不必要的載入時間,我們可以使React.Fragment來避免建立不必要的元素。

function Component() {
        return (
            <React.Fragment>
                <h1>Hello world!</h1>
                <h1>Hello there!</h1>
                <h1>Hello there again!</h1>
            </React.Fragment>
        )
}
複製程式碼

總結

我們文中列出的基本上是React內部提供的效能優化方法,這些方法可以幫助React更好地執行,並沒有列出例如Immutable.js第三方工具庫的優化方法。其實效能優化的方法有很多,但正如上面所說的,合適的方法也要在合適的場景下使用,過度的使用效能優化反而會得不償失。