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é)果
我們發(fā)現(xiàn)純組件只執(zhí)行了一次杈曲,以后在改相同的值的時(shí)候驰凛,并沒有再重新渲染組件,而非純組件則是每次更改都在重新渲染担扑,所以純組件要比非純組件更節(jié)約性能
3. 函數(shù)組件來實(shí)現(xiàn)純組件 memo
-
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é)果
在不使用 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é)果
現(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>
}
}
我們打開瀏覽器等待兩秒
發(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è)俅蜷_瀏覽器等待兩秒之后
我們只改變了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ò)
未捕獲錯(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方法中不要做以下事情
- 不要調(diào)用 setState 方法去更改狀態(tài)阴颖、
- 不要使用其他手段查詢更改 DOM 元素,以及其他更改應(yīng)用程序的操作丐膝、
- 不要在componentWillUpdate生命周期中重復(fù)調(diào)用setState方法更改狀態(tài)量愧、
- 不要在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文件
- main.[hash].chunk.js:這是你的應(yīng)用程序代碼愈诚,App.js 等.
- 1.[hash].chunk.js:這是第三方庫的代碼,包含你在 node_modules 中導(dǎo)入的模塊.
- 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
優(yōu)化后的