在咱們日常的 React 開發中,咱們肆無忌憚的編寫着代碼,項目剛開始可能沒什麼大問題,可是隨着項目愈來愈大,功能愈來愈多,問題就慢慢的就凸顯出來了。爲了讓項目可以正常、穩定的運行,咱們在編寫代碼的時候應該多思考,代碼該怎麼劃分、該怎麼編寫。javascript
如下爲總結的一些關於react優化相關的知識:java
React.PureComponent
和React.memo
很類似,都是可以在必定的條件下減小組件的從新渲染,提升性能。react
React.PureComponent
與React.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
和React.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);
複製代碼
在這個函數裏面,咱們能夠本身來決定是否從新渲染組件,返回 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()
應該始終清楚咱們什麼狀況下才能阻止從新渲染。
當咱們在 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 幫助 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。
在大多數狀況下,咱們也會將代碼以路由爲界限進行分割,來提高界面首次加載速度。