react 性能優(yōu)化

react 性能優(yōu)化

React 組件性能優(yōu)化的核心就是減少渲染真實(shí)DOM節(jié)點(diǎn)的頻率焰望,減少Virtual DOM 對(duì)比的頻率儿礼,以此來提高性能

1. 組件卸載之前進(jìn)行清理操作

在組件中為window 注冊(cè)的全局事件械念,以及定時(shí)器不脯,在組件卸載前要清理掉,防止組件卸載后繼續(xù)執(zhí)行影響應(yīng)用性能

我們開啟一個(gè)定時(shí)器然后卸載組件劫拗,查看組件中的定時(shí)器是否還在運(yùn)行 Test 組件來開啟一個(gè)定時(shí)器

import {useEffect} from 'react'

export default function Test () {
  useEffect(() => {
    setInterval(() => {
      console.log('定時(shí)器開始執(zhí)行')
    }, 1000)
  }, [])
  return <div>Test</div>
}

在App.js中引入定時(shí)器組件然后用flag變量來控制渲染和卸載組件

import Test from "./Test";
import { useState } from "react"
function App() {
  const [flag, setFlag] = useState(true)
  return (
    <div>
      { flag && <Test /> }
      <button onClick={() => setFlag(prev => !prev)}>點(diǎn)擊按鈕</button>
    </div>
  );
}

export default App;

在瀏覽器中我們?nèi)c(diǎn)擊按鈕發(fā)現(xiàn)組件被卸載后定時(shí)器還在執(zhí)行,這樣組件太多之后或者這個(gè)組件不停的渲染和卸載會(huì)開啟很多的定時(shí)器,我們應(yīng)用的性能肯定會(huì)被拉垮刊棕,所以我們需要在組建卸載的時(shí)候去銷毀定時(shí)器。

import {useEffect} from 'react'

export default function Test () {
  useEffect(() => {
    // 因?yàn)橐N毀定時(shí)器所以我們需要用一個(gè)變量來接受定時(shí)器id
    const InterValTemp =  setInterval(() => {
      console.log('定時(shí)器開始執(zhí)行')
    }, 1000)
    return () => {
      console.log(`ID為${InterValTemp}定時(shí)器被銷毀了`)
      clearInterval(InterValTemp)
    }
  }, [])
  return <div>Test</div>
}

這個(gè)時(shí)候我們?cè)谌c(diǎn)擊銷毀組建的時(shí)候定時(shí)器就被銷毀掉了

2. 類組件用純組件來提升組建性能PureComponent

1. 什么是純組件

    純組件會(huì)對(duì)組建的輸入數(shù)據(jù)進(jìn)行淺層比較待逞,如果輸入數(shù)據(jù)和上次輸入數(shù)據(jù)相同甥角,組建不會(huì)被重新渲染

2. 什么是淺層比較

    比較引用數(shù)據(jù)類型在內(nèi)存中的引用地址是否相同,比較基本數(shù)據(jù)類型的值是否相同

3. 如何實(shí)現(xiàn)純組件

    類組件集成 PureComponent 類飒焦,函數(shù)組件使用memo方法

4. 為什么不直接進(jìn)行diff操作蜈膨,而是要進(jìn)行淺層比較,淺層比較難到?jīng)]有性能消耗嗎

    和進(jìn)行 diff 比較操作相比牺荠,淺層比較小號(hào)更少的性能翁巍,diff 操作會(huì)重新遍歷整個(gè) virtualDOM 樹,而淺層比較只比較操作當(dāng)前組件的     state和props

在狀態(tài)中存儲(chǔ)一個(gè)name為張三的休雌,在組建掛載后我們每隔1秒更改name的值為張三灶壶,然后我們看純組件和非純組件,查看結(jié)果

// 純組件
import { PureComponent } from 'react'
class PureComponentDemo extends PureComponent {
  render () {
    console.log("純組件")
    return <div>{this.props.name}</div>
  }
}
// 非純組件
import { Component } from 'react'
class ReguarComponent extends Component {
 render () {
   console.log("非純組件")
   return <div>{this.props.name}</div>
 }
}

引入純組件和非純組件 并在組件掛在后開啟定時(shí)器每隔1秒更改name的值為張三

import { Component } from 'react'
import { ReguarComponent, PureComponentDemo } from './PureComponent'
class App extends Component {
  constructor () {
    super()
    this.state = {
      name: '張三'
    }
  }
  updateName () {
    setInterval(() => {
      this.setState({name: "張三"})
    }, 1000)
  }
  componentDidMount () {
    this.updateName()
  }
  render () {
    return <div>
      <ReguarComponent name={this.state.name}></ReguarComponent>
      <PureComponentDemo name={this.state.name}></PureComponentDemo>
    </div>
  }
}

打開瀏覽器查看執(zhí)行結(jié)果

image-20210922214700974

我們發(fā)現(xiàn)純組件只執(zhí)行了一次杈曲,以后在改相同的值的時(shí)候驰凛,并沒有再重新渲染組件,而非純組件則是每次更改都在重新渲染担扑,所以純組件要比非純組件更節(jié)約性能

3. 函數(shù)組件來實(shí)現(xiàn)純組件 memo

  1. memo 基本使用

    將函數(shù)組件變成純組件恰响,將當(dāng)前的props和上一次的props進(jìn)行淺層比較,如果相同就組件組件的渲染涌献∨呋拢》。

我們?cè)诟附M件中維護(hù)兩個(gè)狀態(tài),index和name 開啟定時(shí)器讓index不斷地發(fā)生變化枢劝,name傳遞給子組件井联,查看父組件更新子組件是否也更新了, 我們先不用memo來查看結(jié)果

import { useState, useEffect } from 'react'
function App () {
  const [ name ] = useState("張三")
  const [index, setIndex] = useState(0)

  useEffect(() => {
    setInterval (() => {
      setIndex(prev => prev + 1)
    }, 1000)
  }, [])

  return <div>
    {index}
    <ShowName name={name}></ShowName>
  </div>
}

function ShowName ({name}) {
  console.log("組件被更新")
  return <div>{name}</div>
}

打開瀏覽器查看執(zhí)行結(jié)果

image-20210923231543043

在不使用 memo 來把函數(shù)組件變成純組件的情況下我們發(fā)現(xiàn)子組件隨著父組件更新而一起重新渲染,但是它依賴的值并沒有更新您旁,這樣浪費(fèi)了性能烙常,我們使用 memo 來避免沒必要的更新

import { useState, useEffect, memo } from 'react'

const ShowName = memo(function ShowName ({name}) {
  console.log("組件被更新")
  return <div>{name}</div>
})

function App () {
  const [ name ] = useState("張三")
  const [index, setIndex] = useState(0)

  useEffect(() => {
    setInterval (() => {
      setIndex(prev => prev + 1)
    }, 1000)
  }, [])

  return <div>
    {index}
    <ShowName name={name}></ShowName>
  </div>
}

我們?cè)俅未蜷_瀏覽器查看執(zhí)行結(jié)果

image-20210922222640420

現(xiàn)在index變動(dòng) 子組件沒有重新渲染了,用 memo 把組件變?yōu)榧兘M件之后就避免了依賴的值沒有更新卻跟著父組件一起更新的情況

4. 函數(shù)組件來實(shí)現(xiàn)純組件(為memo方法傳遞自定義比較邏輯)

memo 方法也是淺層比較

memo 方法是有第二個(gè)參數(shù)的第二個(gè)參數(shù)是一個(gè)函數(shù)

這個(gè)函數(shù)有個(gè)兩個(gè)參數(shù)鹤盒,第一個(gè)參數(shù)是上一次的props蚕脏,第二個(gè)參數(shù)是下一個(gè)props

這個(gè)函數(shù)返回 false 代表重新渲染, 返回true 重新渲染

比如我們有員工姓名和職位兩個(gè)數(shù)據(jù)侦锯,但是頁面中只使用了員工姓名蝗锥,那我們只需要觀察員工姓名發(fā)生變動(dòng)沒有,所以我們?cè)趍emo的第二個(gè)參數(shù)去比較是否需要重新渲染

import { useState, useEffect, memo } from 'react'

function compare (prevProps, nextProps) {
  if (prevProps.person.name !== nextProps.person.name) {
    return false
  }
  return true
}

const ShowName = memo(function ShowName ({person}) {
  console.log("組件被更新")
  return <div>{person.name}</div>
}, compare)

function App () {
  const [ person, setPerson ] = useState({ name: "張三", job: "工程師"})

  useEffect(() => {
    setInterval (() => {
      setPerson({
        ...person,
        job: "挑糞"
      })
    }, 1000)
  }, [person])

  return <div>
    <ShowName person={person}></ShowName>
  </div>
}

5. shouldComponentUpdata

純組件只能進(jìn)行淺層比較率触,要進(jìn)行深層次比較,使用 shouldComponentUpdate汇竭,它用于編寫自定義比較邏輯

返回true 重新渲染組件葱蝗, 返回 false 組件重新渲染組件

函數(shù)的第一個(gè)參數(shù)為 nextProps,第二個(gè)參數(shù)為NextState

比如我們有員工姓名和職位兩個(gè)數(shù)據(jù)细燎,但是頁面中只使用了員工姓名两曼,那我們只需要觀察員工姓名發(fā)生變動(dòng)沒有,利用shouldComponentUpdata來控制只有員工姓名發(fā)生變動(dòng)才重新渲染組件玻驻,我們查看使用 shouldComponentUpdata 生命周期函數(shù)和不使用shouldComponentUpdata生命周期函數(shù)的區(qū)別

// 沒有使用的組件
import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      person: {
        name: '張三',
        job: '工程師'
      }
    }
  }
  componentDidMount (){
    setTimeout (() => {
      this.setState({
        person: {
          ...this.state.person,
          job: "修水管"
        }
      })
    }, 2000) 
  }
  render () {
    console.log("render 方法執(zhí)行了")
    return <div>
      {this.state.person.name}
    </div>
  }
}

我們打開瀏覽器等待兩秒

image-20210922220251277

發(fā)現(xiàn)render方法執(zhí)行了兩次悼凑,組件被重新渲染了,但是我們并沒有更改name 屬性璧瞬,所以這樣浪費(fèi)了性能户辫,我們用shouldComponentUpdata生命周期函數(shù)來判斷name是否發(fā)生了改變

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      person: {
        name: '張三',
        job: '工程師'
      }
    }
  }
  componentDidMount (){
    setTimeout (() => {
      this.setState({
        person: {
          ...this.state.person,
          job: "修水管"
        }
      })
    }, 2000) 
  }
  render () {
    console.log("render 方法執(zhí)行了")
    return <div>
      {this.state.person.name}
    </div>
  }
  shouldComponentUpdate (nextProps, nextState) {
    if (this.state.person.name !== nextState.person.name) {
      return true;
    }
    return false;
  }
}

我們?cè)俅蜷_瀏覽器等待兩秒之后

image-20210922220711461

我們只改變了job 的時(shí)候render方法只執(zhí)行了一次,這樣就減少了沒有必要的渲染嗤锉,從而節(jié)約了性能

6. 使用組件懶加載

使用路由懶加載可以減少bundle文件大小渔欢,從而加快組建呈遞速度

創(chuàng)建 Home 組建

// Home.js
function Home() {
  return (
    <div>
      首頁
    </div>
  )
}

export default Home

創(chuàng)建 List 組建

// List.js
function List() {
  return (
    <div>
      列表頁
    </div>
  )
}

export default List

從react-router-dom包中引入 BrowserRouter, Route, Switch, Link 和 home 與list 來創(chuàng)建路由規(guī)則以及切換區(qū)域和跳轉(zhuǎn)按鈕

import { BrowserRouter, Route, Switch, Link } from 'react-router-dom'
import Home from './Home';
import List from './List';

function App () {
  return <div>
    <BrowserRouter>
        <Link to="/">首頁</Link>
        <Link to="/list">列表頁</Link>
      <Switch>
          <Route path="/" exact component={Home}></Route>
          <Route path="/list" component={List}></Route>
      </Switch>
    </BrowserRouter>
  </div>
}

使用 lazy, Suspense 來創(chuàng)建加載區(qū)域與加載函數(shù)

import { lazy, Suspense } from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom'

const Home = lazy(() => import('./Home'))
const List = lazy(() => import('./List'))

function Loading () {
  return <div>loading</div>
}

function App () {
  return <div>
    <BrowserRouter>
        <Link to="/">首頁</Link>
        <Link to="/list">列表頁</Link>
      <Switch>
        <Suspense fallback={<Loading />}>
          <Route path="/" exact component={Home}></Route>
          <Route path="/list" component={List}></Route>
        </Suspense>
      </Switch>
    </BrowserRouter>
  </div>
}

使用注解方式來為打包后的文件命名

const Home = lazy(() => import(/* webpackChunkName: "Home"  */'./Home'))
const List = lazy(() => import(/* webpackChunkName: "List" */'./List'))

7. 根據(jù)條件進(jìn)行組件懶加載

適用于組件不會(huì)隨條件頻繁切換

import { lazy, Suspense } from 'react';


function App () {
  let LazyComponent = null;
  if (false){
    LazyComponent = lazy(() => import(/* webpackChunkName: "Home"  */'./Home'))
  } else {
    LazyComponent = lazy(() => import(/* webpackChunkName: "List" */'./List'))
  }
  return <div>
    <Suspense fallback={<div>loading</div>}>
      <LazyComponent />
    </Suspense>
  </div>
}

export default App;

這樣就只會(huì)加載一個(gè)組件從而提升性能

8. 通過使用占位符標(biāo)記提升React組件的渲染性能

React組件中返回的jsx如果有多個(gè)同級(jí)元素必須要有一個(gè)共同的父級(jí)

function App () {
  return (<div>
        <div>1</div>
      <div>2</div>
    </div>)
}

為了滿足這個(gè)條件我們通常會(huì)在外面加一個(gè)div,但是這樣的話就會(huì)多出一個(gè)無意義的標(biāo)記瘟忱,如果每個(gè)元素都多處這樣的一個(gè)無意義標(biāo)記的話奥额,瀏覽器渲染引擎的負(fù)擔(dān)就會(huì)加劇

為了解決這個(gè)問題,React 推出了 fragment 占位符標(biāo)記访诱,使用占位符編輯既滿足了共同父級(jí)的要求垫挨,也不會(huì)渲染一個(gè)無意義的標(biāo)記

import { Fragment } from 'react'
function App () {
  return <Fragment>
        <div>1</div>
        <div>1</div>
  </Fragment>
}

當(dāng)然 fragment 標(biāo)記還是太長(zhǎng)了,所以有還有簡(jiǎn)寫方法

function App () {
  return <>
        <div>1</div>
        <div>1</div>
  </>
}

9. 不要使用內(nèi)聯(lián)函數(shù)定義

在使用內(nèi)聯(lián)函數(shù)后触菜,render 方法每次運(yùn)行后都會(huì)創(chuàng)建該函數(shù)的新實(shí)例九榔,導(dǎo)致 React 在進(jìn)行 Virtual DOM 對(duì)比的時(shí)候,新舊函數(shù)比對(duì)不相等,導(dǎo)致 React 總是為元素綁定新的函數(shù)實(shí)例帚屉,而舊的函數(shù)有要交給垃圾回收器處

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      name: '張三'
    }
  }
  render () {
    return <div>
      <h3>{this.state.name}</h3>
      <button onClick={() => { this.setState({name: "李四"})}}>修改</button>
    </div>
  }
}


export default App;

修改為以下的方式

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      name: '張三'
    }
  }
  render () {
    return <div>
      <h3>{this.state.name}</h3>
      <button onClick={this.setChangeName}>修改</button>
    </div>
  }
  setChangeName = () => {
    this.setState({name: "李四"})
  }
}

10. 在構(gòu)造函數(shù)中進(jìn)行函數(shù)this綁定

在類組件中如果使用 fn(){} 這種方式定義函數(shù)谜诫,函數(shù)的 this 指向默認(rèn)只想 undefined,也就是說函數(shù)內(nèi)部的 this 指向需要被更正攻旦,

可以在構(gòu)造函數(shù)中對(duì)函數(shù)進(jìn)行 this 更正喻旷,也可以在內(nèi)部進(jìn)行更正,兩者看起來沒有太大差別牢屋,但是對(duì)性能影響是不同的

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      name: '張三'
    }
    // 這種方式應(yīng)為構(gòu)造器只會(huì)執(zhí)行一次所以只會(huì)執(zhí)行一次
    this.setChangeName = this.setChangeName.bind(this)
  }
  render () {
    return <div>
      <h3>{this.state.name}</h3>
      {/* 這種方式在render方法執(zhí)行的時(shí)候就會(huì)生成新的函數(shù)實(shí)例 */}
      <button onClick={this.setChangeName.bind(this)}>修改</button>
    </div>
  }
  setChangeName() {
    this.setState({name: "李四"})
  }
}

在構(gòu)造函數(shù)中更正this指向只會(huì)更正一次且预,而在render方法中如果不更正this指向的話 那么就是 undefined ,但是在render方法中更正的話render方法的每次執(zhí)行都會(huì)返回新的函數(shù)實(shí)例這樣是對(duì)性能是有所影響的

11. 類組件中的箭頭函數(shù)

在類組件中使用箭頭函數(shù)不會(huì)存在this指向問題烙无,因?yàn)榧^函數(shù)不綁定this

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      name: '張三'
    }
  }
  render () {
    return <div>
      <h3>{this.state.name}</h3>
      {/* <button onClick={() => { this.setState({name: "李四"})}}>修改</button> */}
      <button onClick={this.setChangeName}>修改</button>
    </div>
  }
  setChangeName = () => {
    this.setState({name: "李四"})
  }
}

箭頭函數(shù)在this指向上確實(shí)比較有優(yōu)勢(shì)

但是箭頭函數(shù)在類組件中作為成員使用的時(shí)候锋谐,該函數(shù)會(huì)被添加成實(shí)例對(duì)象屬性,而不是原型對(duì)象屬性截酷,如果組件被多次重用涮拗,每個(gè)組件實(shí)例都會(huì)有一個(gè)相同的函數(shù)實(shí)例,降低了函數(shù)實(shí)例的可用性造成了資源浪費(fèi)

綜上所述迂苛,我們得出結(jié)論三热,在使用類組件的時(shí)候還是推薦在構(gòu)造函數(shù)中通過使用bind方法更正this指向問題

12. 避免使用內(nèi)聯(lián)樣式屬性

當(dāng)使用內(nèi)聯(lián)樣式的時(shí)候,內(nèi)聯(lián)樣式會(huì)被編譯成JavaScript代碼三幻,通過javascript代碼將樣式規(guī)則映射到元素身上就漾,瀏覽器就會(huì)畫更多的時(shí)間執(zhí)行腳本和渲染UI,從而增加了組件的渲染時(shí)間

function App () {
  return <div style={{backgroundColor: 'red';}}></div>
}

在上面的組件中念搬,為元素增加了背景顏色為紅色抑堡,這個(gè)樣式為JavaScript對(duì)象,背景顏色需要被轉(zhuǎn)換成等效的css規(guī)則朗徊,然后應(yīng)用到元素上首妖,這樣涉及了腳本的執(zhí)行,實(shí)際上內(nèi)聯(lián)樣式的問題在于是在執(zhí)行的時(shí)候?yàn)樵靥砑訕邮饺偾悖皇窃诰幾g的時(shí)候?yàn)樵靥砑訕邮?/p>

更好的方式是導(dǎo)入樣式文件悯搔,能通過css直接做的事情就不要通過JavaScript來做,因?yàn)镴avaScript操作 DOM 非常慢

13. 優(yōu)化條件渲染以提升組件性能

頻繁的掛在和卸載組件是一件非常耗性能的事情舌仍,應(yīng)該減少組件的掛載和卸載次數(shù)妒貌,

在React中 我們經(jīng)常會(huì)通過不同的條件渲染不同的組件,條件渲染是一必須做的優(yōu)化操作.

function App () {
  if (true) {
    return <div>
      <Component1 />
        <Component2 />
      <Component3 />
    </div>
  } else {
    return <div>
        <Component2 />
        <Component3 />
    </div>
  }
  
}

上面的代碼中條件不同的時(shí)候铸豁,React 內(nèi)部在進(jìn)行Virtual DOM 對(duì)比的時(shí)候發(fā)現(xiàn)第一個(gè)元素和第二個(gè)元素都已經(jīng)發(fā)生變化灌曙,所以會(huì)卸載組件1、組件2节芥、組件3在刺,然后再渲染組件2逆害、組件3。實(shí)際上變化的只有組件1蚣驼,重新掛在組件2和組件3時(shí)沒有必要的

function App () {
  if (true) {
    return <div>
      { true && <Component1 />}
        <Component2 />
      <Component3 />
    </div>
  }
}

這樣變化的就只有組件1了節(jié)省了不必要的渲染

16. 避免重復(fù)的無限渲染

當(dāng)應(yīng)用程序狀態(tài)更改的時(shí)候魄幕,React 會(huì)調(diào)用 render方法 如果在render方法中繼續(xù)更改應(yīng)用程序狀態(tài),就會(huì)發(fā)生遞歸調(diào)用導(dǎo)致應(yīng)用報(bào)錯(cuò)

image-20210923220549762

未捕獲錯(cuò)誤:超出最大更新深度颖杏。當(dāng)組件在componentWillUpdate或componentDidUpdate內(nèi)重復(fù)調(diào)用setState時(shí)纯陨,可能會(huì)發(fā)生這種情況。React限制嵌套更新的數(shù)量以防止無限循環(huán)留储。React限制的最大次數(shù)為50次

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      name: '張三'
    }
  }
  render () {
    this.setState({name:"張五"})
    return <div>
      <h3>{this.state.name}</h3>
      <button onClick={this.setChangeName}>修改</button>
    </div>
  }
  setChangeName = () => {
    this.setState({name: "李四"})
  }
}

與其他生命周期函數(shù)不同翼抠,render 方法應(yīng)該被作為純函數(shù),這意味著获讳,在render方法中不要做以下事情

  1. 不要調(diào)用 setState 方法去更改狀態(tài)阴颖、
  2. 不要使用其他手段查詢更改 DOM 元素,以及其他更改應(yīng)用程序的操作丐膝、
  3. 不要在componentWillUpdate生命周期中重復(fù)調(diào)用setState方法更改狀態(tài)量愧、
  4. 不要在componentDidUpdate生命周期中重復(fù)調(diào)用setState方法更改狀態(tài)、

render方法執(zhí)行根據(jù)狀態(tài)改變執(zhí)行帅矗,這樣可以保持組件的行為與渲染方式一致

15. 為組件創(chuàng)建錯(cuò)誤邊界

默認(rèn)情況下侠畔,組件渲染錯(cuò)誤會(huì)導(dǎo)致整個(gè)應(yīng)用程序中斷,創(chuàng)建錯(cuò)誤邊界可以確保組件在發(fā)生錯(cuò)誤的時(shí)候應(yīng)用程序不會(huì)中斷损晤,錯(cuò)誤邊界是一個(gè)React組件,可以捕獲子級(jí)組件在渲染是發(fā)生錯(cuò)誤红竭,當(dāng)錯(cuò)誤發(fā)生時(shí)尤勋,可以記錄下來,可以顯示備用UI界面茵宪,

錯(cuò)誤邊界涉及到兩個(gè)生命周期最冰,分別是 getDerivedStateFromError 和 componentDidCatch.

getDerivedStateFromError 為靜態(tài)方法,方法中需要返回一個(gè)對(duì)象稀火,該對(duì)象會(huì)和state對(duì)象進(jìn)行合并暖哨,用于更改應(yīng)用程序狀態(tài).

componentDidCatch 方法用于記錄應(yīng)用程序錯(cuò)誤信息,該方法返回的是錯(cuò)誤對(duì)象

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      hasError: false
    }
  }
  componentDidCatch (error) {
    console.log(error)
  }
  static getDerivedStateFromError () {
    return {
      hasError: true
    }
  }
  render () {
    if (this.state.hanError) {
      return <div>
        發(fā)生錯(cuò)誤了
      </div>
    }
    return <Test></Test>
  }
}

class Test extends Component {
  constructor () {
    super()
    this.state = {
      hanError: false
    }
  }
  render () {
    throw new Error("發(fā)生了錯(cuò)誤");
    return <div>
      正確的
    </div>
  }
}

當(dāng)我們拋出錯(cuò)誤的時(shí)候凰狞,getDerivedStateFromError 會(huì)合并返回的對(duì)象到state 所以hasError會(huì)變成true 就會(huì)渲染我們備用的界面了

注意: getDerivedStateFromError 不能捕獲異步錯(cuò)誤篇裁,譬如按鈕點(diǎn)擊事件發(fā)生后的錯(cuò)誤

16. 避免數(shù)據(jù)結(jié)構(gòu)突變

組件中 props 和 state 的數(shù)據(jù)結(jié)構(gòu)應(yīng)該保持一致,數(shù)據(jù)結(jié)構(gòu)突變會(huì)導(dǎo)致輸出不一致

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      man: {
        name: "張三",
        age: 18
      }
    }
    this.setMan = this.setMan.bind(this)
  }
  render () {
    const { name, age } = this.state.man
    return <div>
      <p>
        {name}
        {age}
      </p>
      <button onClick={this.setMan}>修改</button>
    </div>
  }
  setMan () {
    this.setState({
      ...this.state,
      man: {
        name: "李四"
      }
    })
  }
}

乍一看這個(gè)代碼貌似沒有問題赡若,仔細(xì)一看我們發(fā)現(xiàn)达布,在我們修改了名字之后年齡字段丟失了,因?yàn)閿?shù)據(jù)突變了 逾冬,我們應(yīng)該去避免這樣的數(shù)據(jù)突變

import { Component } from 'react'
class App extends Component {
  constructor () {
    super()
    this.state = {
      man: {
        name: "張三",
        age: 18
      }
    }
    this.setMan = this.setMan.bind(this)
  }
  render () {
    const { name, age } = this.state.man
    return <div>
      <p>
        {name}
        {age}
      </p>
      <button onClick={this.setMan}>修改</button>
    </div>
  }
  setMan () {
    this.setState({
      man: {
        ...this.state.man,
        name: "李四"
      }
    })
  }
}

17. 依賴優(yōu)化

在應(yīng)用程序中我們經(jīng)常使用地三方的包黍聂,但我們不想引用包中的所有代碼躺苦,我們只想用到那些代碼就包含那些代碼,此時(shí)我們可以使用插件對(duì)依賴項(xiàng)進(jìn)行優(yōu)化

我們使用 lodash 舉例子. 應(yīng)用基于 create-react-app 腳手架創(chuàng)建

1. 下載依賴

npm install react-app-rewired customize-cra lodash babel-plugin-lodash

react-app-rewired: 覆蓋create-react-app 配置

module.exports = function (oldConfig) {
    return  newConfig
}

customize-cra: 導(dǎo)出輔助方法产还,可以讓以上寫法更簡(jiǎn)潔

const { override, useBabelRc } = require("customize-cra")
module.exports = override(
    (oldConfig) => newConfig,   
  (oldConfig) => newConfig,
)

override: 可以接收多個(gè)參數(shù)匹厘,每個(gè)參數(shù)都是一個(gè)配置函數(shù),函數(shù)接受oldConfig脐区,返回newConfig

useBabelRc:允許使用.babelrc 文件進(jìn)行babel 配置

babel-plugin-lodash:對(duì)lodash 進(jìn)行精簡(jiǎn)

2. 在項(xiàng)目的根目錄新建 config-overrides.js 并加入以下配置

const { override, useBabelRc } = require("customize-cra")

module.exports = override(useBabelRc())

3. 修改package.json文件中的構(gòu)建命令

{
  "script": {
       "start": "react-app-rewired start",
       "build": "react-app-rewired build",
       "test": "react-app-rewired test --env=jsdom",
       "eject": "react-scripts eject"
  }
}

4. 創(chuàng)建 .babelrc 文件并加入配置

{
  "plugins": ["lodash"]
}

5. 生產(chǎn)環(huán)境下的三種 JS文件

  1. main.[hash].chunk.js:這是你的應(yīng)用程序代碼愈诚,App.js 等.
  2. 1.[hash].chunk.js:這是第三方庫的代碼,包含你在 node_modules 中導(dǎo)入的模塊.
  3. runtime~main.[hash].js:webpack 運(yùn)行時(shí)代碼.

6. App 組件中代碼

import _ from 'lodash'

function App () {
   console.log(_.chunk(['a', 'b', 'c', 'd']))
  return    <div>Test</div>
}

沒有引入lodash


沒有引入lodash

引入lodash


引入lodash

優(yōu)化后的


優(yōu)化后的
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坡椒,一起剝皮案震驚了整個(gè)濱河市扰路,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倔叼,老刑警劉巖汗唱,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丈攒,居然都是意外死亡哩罪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門巡验,熙熙樓的掌柜王于貴愁眉苦臉地迎上來际插,“玉大人,你說我怎么就攤上這事显设】虺冢” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵捕捂,是天一觀的道長(zhǎng)瑟枫。 經(jīng)常有香客問我,道長(zhǎng)指攒,這世上最難降的妖魔是什么慷妙? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮允悦,結(jié)果婚禮上膝擂,老公的妹妹穿的比我還像新娘。我一直安慰自己隙弛,他們只是感情好架馋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著全闷,像睡著了一般绩蜻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上室埋,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天办绝,我揣著相機(jī)與錄音伊约,去河邊找鬼。 笑死孕蝉,一個(gè)胖子當(dāng)著我的面吹牛屡律,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播降淮,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼超埋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了佳鳖?” 一聲冷哼從身側(cè)響起霍殴,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎系吩,沒想到半個(gè)月后来庭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡穿挨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年月弛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片科盛。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帽衙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贞绵,到底是詐尸還是另有隱情厉萝,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布榨崩,位于F島的核電站冀泻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蜡饵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一胳施、第九天 我趴在偏房一處隱蔽的房頂上張望溯祸。 院中可真熱鬧,春花似錦舞肆、人聲如沸焦辅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筷登。三九已至,卻和暖如春哩盲,著一層夾襖步出監(jiān)牢的瞬間前方,已是汗流浹背狈醉。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惠险,地道東北人苗傅。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像班巩,于是被迫代替她去往敵國(guó)和親渣慕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • React 為高性能應(yīng)用設(shè)計(jì)提供了許多優(yōu)化方案抱慌,本文列舉了其中的一些最佳實(shí)踐逊桦。 在以下場(chǎng)景中,父組件和子組件通常會(huì)...
    Maco_wang閱讀 1,101評(píng)論 0 7
  • 哪些場(chǎng)景下抑进,父組件和子組件會(huì)重新渲染强经? 1.在同一組件或父組件中調(diào)用 setState 時(shí)。 2.從父級(jí)收到的“p...
    CodeBetter閱讀 524評(píng)論 1 0
  • tips:歡迎關(guān)注我在github的博客點(diǎn)擊查看 单匣。 使用React.pureComponent夕凝,React.me...
    aermin閱讀 1,296評(píng)論 0 1
  • 前言 關(guān)于React性能優(yōu)化,有各種方法户秤。今天码秉,我們主要使用兩個(gè)官方推出的組件模式來進(jìn)行切入,優(yōu)化點(diǎn)主要基于防止組...
    南宮__閱讀 1,112評(píng)論 0 9
  • 有個(gè)很重要的方向:就是避免不必要的渲染鸡号。 React將render函數(shù)返回的虛擬DOM樹與老的進(jìn)行比較转砖,從而確定D...
    GC風(fēng)暴閱讀 274評(píng)論 0 0