提高應用程序性能的一種方法是實現memoization。Memoization是一種優化技術,主要經過存儲昂貴的函數調用的結果,並在再次發生相同的輸入時返回緩存的結果,以此來加速程序。
父組件的每次狀態更新,都會致使子組件從新渲染,即便傳入子組件的狀態沒有變化,爲了減小重複渲染,咱們可使用React.memo來緩存組件,這樣只有當傳入組件的狀態值發生變化時纔會從新渲染。若是傳入相同的值,則返回緩存的組件。示例以下:javascript
export default React.memo((props) => {
return (
<div>{props.value}</div>
)
});
複製代碼
有時渲染是不可避免的,但若是您的組件是一個功能組件,從新渲染會致使每次都調用大型計算函數,這是很是消耗性能的,咱們可使用新的useMemo鉤子來「記憶」這個計算函數的計算結果。這樣只有傳入的參數發生變化後,該計算函數纔會從新調用計算新的結果。
經過這種方式,您可使用從先前渲染計算的結果來挽救昂貴的計算耗時。整體目標是減小JavaScript在呈現組件期間必須執行的工做量,以便主線程被阻塞的時間更短。java
// 避免這樣作
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} />
}
複製代碼
父組件狀態的每次更新,都會致使子組件的從新渲染,即便是傳入相同props。可是這裏的從新渲染不是說會更新DOM,而是每次都會調用diif算法來判斷是否須要更新DOM。這對於大型組件例如組件樹來講是很是消耗性能的。
在這裏咱們就可使用React.PureComponent , shouldComponentUpdate生命週期來確保只有當組件props狀態改變時纔會從新渲染。以下例子:react
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 進行以下優化:git
// 第一種優化
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來包裹小組件,也能夠提高性能。github
使用內聯對象時,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> ) } 複製代碼
渲染成本很高,尤爲是在須要更改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規定組件中必須有一個父元素。函數
<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第三方工具庫的優化方法。其實性能優化的方法有不少,但正如上面所說的,合適的方法也要在合適的場景下使用,過分的使用性能優化反而會得不償失。
參考連接
原文在這裏:gitHub 若有遺漏,還請指正!!