本文示例javascript
需求: 編寫個父子組件java
父組件react
import React, { useState } from 'react'
import Child from './child'
export default function Parent(props: any) {
const [num, setNum] = useState(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div> <h2>{num}</h2> <button onClick={handleClick}>更改num</button> {/* 引用子組件 */} <Child /> </div>
)
}
複製代碼
子組件git
export default function Child(props: any) {
console.info('子組件渲染了')
return <div>我是子組件</div>
}
複製代碼
現象:github
每次點擊"更改num"按鈕,控制檯都會彈出"子組件渲染了" web
上面子組件裏並無依賴父組件任何屬性,卻在每次父組件更改時,都會被從新渲染,此時能夠用 React.memo 優化。api
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
複製代碼
React.memo 至關於 PureComponent,是個高階組件,默認對 props 作一次淺比較,若是 props 沒有更改,則子組件不會從新執行。數組
若是想要自定義對比過程,能夠經過第二個參數來實現:markdown
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/* 若是把 nextProps 傳入 render 方法的返回結果與 將 prevProps 傳入 render 方法的返回結果一致則返回 true, 不然返回 false */
}
export default React.memo(MyComponent, areEqual);
複製代碼
import React, { useState } from 'react'
import Child from './child'
const MemoChild = React.memo(Child) // 新增代碼
export default function Parent(props: any) {
const [num, setNum] = useState(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div> <h2>{num}</h2> <button onClick={handleClick}>更改num</button> {/* 引用子組件 */} {/* <Child /> */} {/* 新增代碼 */} <MemoChild /> </div>
)
}
複製代碼
現象: 每次點擊"更改num"按鈕,控制檯都再也不彈出"子組件渲染了" 那麼這個示例是否完美了呢?若是說父子組件之間不通信,那麼這樣就可行了。但父組件要傳數據跟事件給子組件時,就仍然會有問題。app
更改父組件代碼爲:
import React, { useState } from 'react'
import Child from './child'
const MemoChild = React.memo(Child)
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const handleClick = () => {
setNum(num + 1)
}
const handleChange = () => {
console.info(2323)
}
return (
<div> <h2>{num}</h2> <button onClick={handleClick}>更改num</button> {/* 引用子組件 */} {/* 新增代碼 */} {/* 父組件傳遞 handleChange 事件給子組件 */} <MemoChild handleChange={handleChange}/> </div>
)
}
複製代碼
現象:
每次點擊"更改num"按鈕,控制檯都會彈出"子組件渲染了" 當傳遞事件給子組件時,點擊更改 num ,控制檯會再次打印出"子組件渲染了",說明子組件又從新渲染了。
分析緣由:
首先一個組件從新渲染,通常是三種狀況致使的:
很明顯,第一種不是,由於子組件並無任何狀態。
第二種,已經用 React.memo 優化掉了,因此也不是。
那說明就是第三種, 傳遞的 props 改了。
那爲何傳遞的 handleChange 函數會發生改變呢?
由於在函數式組件裏每次從新渲染,都會從新從頭開始執行函數調用,那麼這兩次建立的 handleChange 函數確定發生了改變,因此致使子組件從新渲染。
流程:
找到緣由,那麼就要想法子,在函數沒有改變的狀況下,從新渲染的時候保持函數的引用一致
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
複製代碼
返回一個 memoized 回調函數。
把內聯回調函數及依賴項數組做爲參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將很是有用。
useCallback(fn, deps) 至關於 useMemo(() => fn, deps)。
// 新增引入 useCallback
import React, { useState, useCallback } from 'react'
import Child from './child'
const MemoChild = React.memo(Child)
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const handleClick = () => {
setNum(num + 1)
}
// 修改代碼
// 經過 useCallback 進行記憶 handleChange, 並將記憶的 handleChange 傳遞給 MemoChild
const memoizedCallback = useCallback(() => {
console.info(2323)
},[])
return (
<div> <h2>{num}</h2> <button onClick={handleClick}>更改num</button> {/* 引用子組件 */} <MemoChild handleChange={memoizedCallback} /> </div>
)
}
複製代碼
現象:
每次點擊"更改num"按鈕,控制檯都再也不彈出"子組件渲染了" 能夠看到子組件並不會從新渲染,那麼是否是能夠結束了呢?
不,父子組件之間除了傳遞事件,還會傳遞狀態
import React, { useState } from 'react'
import Child from './child'
const MemoChild = React.memo(Child) // 新增代碼
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const [name] = useState<string>()
const handleClick = () => {
setNum(num + 1)
}
return (
<div> <h2>{num}</h2> <button onClick={handleClick}>更改num</button> {/* 引用子組件 */} {/* 新增代碼 */} {/* 父組件傳遞 name 狀態給子組件 */} <MemoChild name={name} /> </div>
)
}
複製代碼
現象:
每次點擊"更改num"按鈕,控制檯都不會彈出"子組件渲染了"。 這不是正常的嗎?這不是在逗我嗎?不是的,請繼續看下來
上面傳遞的 name 是基本類型,若是是對象呢? 更改代碼以下:
import React, { useState } from 'react'
import Child from './child'
const MemoChild = React.memo(Child)
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div> <h2>{num}</h2> <button onClick={handleClick}>更改num</button> {/* 引用子組件 */} {/* 修改代碼 */} {/* 父組件傳遞 name 狀態給子組件 */} {/* <MemoChild name={name} /> */} <MemoChild person={{name:"張三"}} /> </div>
)
}
複製代碼
現象:
每次點擊"更改num"按鈕,控制檯都會彈出"子組件渲染了"。 實際業務中,傳遞的對象確定更復雜,我這裏只是爲了說明只要是個對象,就會致使子組件從新渲染。
緣由跟上面傳遞事件是同樣的,傳遞對象,每次從新渲染都指向新的引用,子組件 react.memo 進行淺比較,會得出傳遞的 props 不等了,因此從新渲染。
useCallback 是用來處理父子傳遞事件的優化,至於傳遞狀態就要用到 useMemo。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
複製代碼
把「建立」函數和依賴項數組做爲參數傳入 useMemo,它僅會在某個依賴項改變時才從新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。
import React, { useState,useMemo } from 'react'
import Child from './child'
const MemoChild = React.memo(Child)
export default function Parent(props: any) {
const [num, setNum] = useState<number>(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div> <h2>{num}</h2> <button onClick={handleClick}>更改num</button> {/* 採用 useMemo */} <MemoChild person={useMemo(()=>({name:"張三"}),[])} /> </div>
)
}
複製代碼
現象:
每次點擊"更改num"按鈕,控制檯都不會彈出"子組件渲染了"。
這下能夠放心傳遞狀態跟事件了。
React hook 比起類組件有了更大的靈活度和自由,但同時對開發者要求也更高了。由於 Hooks 使用不恰當很容易出現性能問題。 memo,useMemo,useCallback 都是用來提高性能的。
那何時用這幾個 api 呢?
實際上在簡單的應用中,儘可能少用,由於對於簡單應用,就算從新渲染也不會消費多少資源,而採用這幾個 api 時,每次都會對第二個參數進行比較,反而消耗了資源。
固然對於開銷比較大的組件就儘可能用 memo。