網站首頁 編程語言 正文
與PureComponent不同的是PureComponent只是進行淺對比props來決定是否跳過更新數據這個步驟,memo可以自己決定是否更新,但它是一個函數組件而非一個類,但請不要依賴它來“阻止”渲染,因為這會產生 bug。
一般memo用法:
import React from "react"; function MyComponent({props}){ ? ? console.log('111); ? ? return ( ? ? ? ?{props}? ? ) }; function areEqual(prevProps, nextProps) { ? ? if(prevProps.seconds===nextProps.seconds){ ? ? ? ? return true ? ? }else { ? ? ? ? return false ? ? } } export default React.memo(MyComponent,areEqual)
問題描述
我們在處理業務需求時,會用到memo來優化組件的渲染,例如某個組件依賴自身的狀態即可完成更新,或僅在props中的某些數據變更時才需要重新渲染,那么我們就可以使用memo包裹住目標組件,這樣在props沒有變更時,組件不會重新渲染,以此來規避不必要的重復渲染。
下面是我創建的一個公共組件:
type Props = { ?inputDisable?: boolean ?// 是否一直展示輸入框 ?inputVisible?: boolean ?value: any ?min: number ?max: number ?onChange: (v: number) => void } const InputNumber: FC= memo( ?(props: Props) => { ? ?const { inputDisable, max, min, value, inputVisible } = props ? ?const handleUpdate = (e: any, num) => { ? ? ?e.stopPropagation() ? ? ?props.onChange(num) ? ?} ? ?return ( ? ? ? ? ? ? ?{(value !== 0 || inputVisible) && ( ? ? ? ? ?<> ? ? ? ? ? ? ? ?) ?}, ?(prevProps, nextProps) => { ? ?return prevProps.value === nextProps.value && prevProps.min === nextProps.min && prevProps.max === nextProps.max ?} ) export default InputNumberhandleUpdate(e, value - 1)} ? ? ? ? ? ? ?mode='aspectFill' ? ? ? ? ? ?/> ? ? ? ? ? ? handleUpdate(e, parseInt(e.detail.value ? e.detail.value : '0'), 'input')} ? ? ? ? ? ?/> ? ? ? ? ?> ? ? ? ?)} ? ? ? ? = max || min > max) ? ? ? ? ? ?? '../../assets/images/plus-no.png' ? ? ? ? ? ?: '../../assets/images/plus.png')} ? ? ? ? ?onClick={e => handleUpdate(e, value + 1)} ? ? ? ?/> ? ? ?
這個組件是一個自定義的數字選擇器,在memo的第二個參數中設置我們需要的參數,當這些參數有變更時,組件才會重新渲染。
在下面是我們用到這個組件的場景。
type Props = { info: any onUpdate: (items) => void } const CartBrand: FC= (props: Props) => { const { info } = props const [items, setItems] = useState( ? info.items.map(item => { ? // selected默認為false ? ? return { num:1, selected: false } ? }) ) useEffect(() => { ? getCartStatus() }, []) // 獲取info.items中沒有提供,但是展示需要的數據 const getCartStatus = () => { ? setTimeout(() => { ? ? setItems( ? ? ? info.items.map(item => { ? ? ? //更新selected為true ? ? ? ? return {num: 1, selected: true } ? ? ? }) ? ? ) ? }, 1000) } return ( ? ? ? {items.map((item: GoodSku, index: number) => { ? ? ? return ( ? ? ? ? ) } export default CartBrand{ ? ? ? ? ? ? console.log(v, item.selected) ? ? ? ? ? }} ? ? ? ? /> ? ? ? ) ? ? })} ?
這個組件的目的是展示props傳過來的列表,但是列表中有些數據服務端沒有給到,需要你再次通過另一個接口去獲取,我用settimeout替代了獲取接口數據的過程。為了讓用戶在獲取接口的過程中不需要等待,我們先根據props的數據給items設置了默認值。然后在接口數據拿到后再更新items。
但幾秒鐘后我們在子組件InputNumber中更新數據,會看到:
selected依然是false!
這是為什么呢?前面不是把items中所有的selected都改為true了嗎?
我們再打印一下items看看:
似乎在InputNumber中的items依然是初始值。
對于這一現象,我個人理解為memo使用的memoization算法存儲了上一次渲染的items數值,由于InputNumber沒有重新渲染,所以在它的本地狀態中,items一直是初始值。
解決方法
方案一. 使用useRef + forceUpdate方案
我們可以使用useRef來保證items一直是最新的,講useState換為useRef
? type Props = { ? info: any ? onUpdate: (items) => void } const CartBrand: FC= (props: Props) => { ? const { info } = props ? const items = useRef( ? ? info.items.map(item => { ? ? // selected默認為false ? ? ? return { num:1, selected: false } ? ? }) ? ) ? useEffect(() => { ? ? getCartStatus() ? }, []) ?? ? // 獲取info.items中沒有提供,但是展示需要的數據 ? const getCartStatus = () => { ? ? setTimeout(() => { ? ? ? items.current = info.items.map(() => { ? ? ? ? return { num: 1, selected: true } ? ? ? }) ? ? }, 1000) ? } ? return ( ? ? ? ? ? {items.current.map((item: GoodSku, index: number) => { ? ? ? ? return ( ? ? ? ? ? ? ) } export default CartBrand{ ? ? ? ? ? ? ? console.log(v, items) ? ? ? ? ? ? }} ? ? ? ? ? /> ? ? ? ? ) ? ? ? })} ? ?
這樣再打印的時候我們會看到
items中的selected已經變成true了
但是此時如果我們需要根據items中的selected去渲染不同的文字,會發現并沒有變化。
? return ( ? ?? ? ? {items.current.map((item: GoodSku, index: number) => { ? ? ? ? return ( ? ? ? ? ? ? )? ? ? ? ? ? ? ? ? ? ) ? ? ? })} ? ?{item.selected ? '選中' : '未選中'} ? ? ? ? ? ?{ ? ? ? ? ? ? ? ? console.log('selected', items) ? ? ? ? ? ? ? }} ? ? ? ? ? ? /> ? ? ? ? ?
顯示還是未選中
這是因為useRef的值會更新,但不會更新他們的 UI,除非組件重新渲染。因此我們可以手動更新一個值去強制讓組件在我們需要的時候重新渲染。
const CartBrand: FC= (props: Props) => { ? const { info } = props ? // 定義一個state,它在每次調用的時候都會讓組件重新渲染 ? const [, setForceUpdate] = useState(Date.now()) ? const items = useRef ( ? ? info.items.map(item => { ? ? ? return { num: 1, selected: false } ? ? }) ? ) ? useEffect(() => { ? ? getCartStatus() ? }, []) const getCartStatus = () => { ? ? setTimeout(() => { ? ? ? items.current = info.items.map(() => { ? ? ? ? return { num: 1, selected: true } ? ? ? }) ? ? ? setForceUpdate() ? ? }, 5000) ? } ? return ( ? ? ? ? ? {items.current.map((item: GoodSku, index: number) => { ? ? ? ? return ( ? ? ? ? ? ? ) } export default CartBrand? ? ? ? ? ? ? ? ? ? ) ? ? ? })} ? ?{item.selected ? '選中' : '未選中'} ? ? ? ? ? ?{ ? ? ? ? ? ? ? ? console.log('selected', items) ? ? ? ? ? ? ? }} ? ? ? ? ? ? /> ? ? ? ? ?
這樣我們就可以使用最新的items,并保證items相關的渲染不會出錯
方案2. 使用useCallback
在InputNumber這個組件中,memo的第二個參數,我沒有判斷onClick回調是否相同,因為無論如何它都是不同的。
參考這個文章:use react memo wisely
函數對象只等于它自己。讓我們通過比較一些函數來看看:
function sumFactory() { return (a, b) => a + b; } const sum1 = sumFactory(); const sum2 = sumFactory(); console.log(sum1 === sum2); // => false console.log(sum1 === sum1); // => true console.log(sum2 === sum2); // => true
sumFactory()是一個工廠函數。它返回對 2 個數字求和的函數。
函數sum1和sum2由工廠創建。這兩個函數對數字求和。但是,sum1和sum2是不同的函數對象(sum1 === sum2is?false)。
每次父組件為其子組件定義回調時,它都會創建新的函數實例。在自定義比較函數中過濾掉onClick固然可以規避掉這種問題,但是這也會導致我們上述的問題,在前面提到的文章中,為我們提供了另一種解決思路,我們可以使用useCallback來緩存回調函數:
type Props = { ? info: any ? onUpdate: (items) => void } const CartBrand: FC= (props: Props) => { ? const { info } = props ? const [items, setItems] = useState( ? ? info.items.map(item => { ? ? ? return { num: 1, selected: false } ? ? }) ? ) ? useEffect(() => { ? ? getCartStatus() ? }, []) ? // 獲取當前購物車中所有的商品的庫存狀態 ? const getCartStatus = () => { ? ? setTimeout(() => { ? ? ? setItems( ? ? ? ? info.items.map(() => { ? ? ? ? ? return { num: 1, selected: true } ? ? ? ? }) ? ? ? ) ? ? }, 5000) ? } ? // 使用useCallback緩存回調函數 ? const logChange = useCallback( ? ? v => { ? ? ? console.log('selected', items) ? ? }, ? ? [items] ? ) ? return ( ? ? ? ? ? {items.map((item: GoodSku, index: number) => { ? ? ? ? return ( ? ? ? ? ? ? ) }? ? ? ? ? ? ? ? ? ? ) ? ? ? })} ? ?? ? ? ? ?
相應的,我們可以把InputNumber的自定義比較函數去掉。
type Props = { ?inputDisable?: boolean ?// 是否一直展示輸入框 ?inputVisible?: boolean ?value: any ?min: number ?max: number ?onChange: (v: number) => void } const InputNumber: FC= memo( ?(props: Props) => { ? ?const { inputDisable, max, min, value, inputVisible } = props ? ?const handleUpdate = (e: any, num) => { ? ? ?e.stopPropagation() ? ? ?props.onChange(num) ? ?} ? ?return ( ? ? ? ? ? ? ?{(value !== 0 || inputVisible) && ( ? ? ? ? ?<> ? ? ? ? ? ? ? ?) ?} ) export default InputNumberhandleUpdate(e, value - 1)} ? ? ? ? ? ? ?mode='aspectFill' ? ? ? ? ? ?/> ? ? ? ? ? ? handleUpdate(e, parseInt(e.detail.value ? e.detail.value : '0'), 'input')} ? ? ? ? ? ?/> ? ? ? ? ?> ? ? ? ?)} ? ? ? ? = max || min > max) ? ? ? ? ? ?? '../../assets/images/plus-no.png' ? ? ? ? ? ?: '../../assets/images/plus.png')} ? ? ? ? ?onClick={e => handleUpdate(e, value + 1)} ? ? ? ?/> ? ? ?
這樣在items更新的時候,inputNumber也會刷新,不過在復雜的邏輯中,比如items的結構非常復雜,items中很多字段都會有高頻率的改變,那這種方式會減弱InputNumber中memo的效果,因為它會隨著items的改變而刷新。
總結
在最后,我還是選擇了方案一解決這個問題。同時提醒自己,memo的使用要謹慎??
原文鏈接:https://juejin.cn/post/7072199274531913765
相關推薦
- 2022-07-11 spring boot中動態代理導致自定義注解掃描失敗以及解決辦法
- 2023-05-22 解決The?Network?Adapter?could?not?establish?the?conn
- 2023-05-15 golang判斷結構體為空的問題_Golang
- 2023-01-14 Go庫text與template包使用示例詳解_Golang
- 2023-10-09 grid網格布局
- 2023-01-11 jQuery綁定點擊事件與改變事件的方式總結及多個元素綁定多個事件_jquery
- 2022-03-22 聚合函數和group?by的關系(理解group by 和聚合函數)
- 2022-04-17 小程序中元素滾動到元素的底部, 一直保持在底部
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支